5d5d861bb92f89795056e08f1d7c3b60bcd5e550
[platform/core/system/memps.git] / memps.c
1 /* Copyright 2014 Samsung Electronics Co., Ltd All Rights Reserved
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <stdbool.h>
19 #include <math.h>
20 #include <string.h>
21 #include <errno.h>
22 #include <unistd.h>
23 #include <fcntl.h>
24 #include <sys/stat.h>
25 #include <sys/types.h>
26 #include <sys/vfs.h>
27 #include <linux/limits.h>
28 #include <limits.h>
29
30 #include <ctype.h>
31 #include <stddef.h>
32
33 #include <dirent.h>
34 #include <sys/utsname.h>
35
36 #define STR_SGX_PATH    "/dev/pvrsrvkm"
37 #define STR_3D_PATH1    "/dev/mali"
38 #define STR_3D_PATH2    "/dev/kgsl-3d0"
39 #define STR_3D_UNIFIED_PATH "/usr/bin/gpu_mem_info"
40 #define STR_DRM_PATH1   "/drm mm object (deleted)"
41 #define STR_DRM_PATH2   "/dev/dri/card0"
42 #define MEMCG_PATH      "/sys/fs/cgroup/memory"
43 #define ZRAM_USED_PATH  "/sys/block/zram0/mem_used_total"
44 #define ZRAM_MM_STAT_PATH       "/sys/block/zram0/mm_stat"
45 #define OOM_SCORE_ADJ_STR       "oom_score_adj"
46 #define OOM_SCORE_STR   "oom_score"
47
48 #define BUF_MAX         (BUFSIZ)            /* most optimal for libc::stdio */
49 #define BUF_INC_SIZE    (512 * 1024)        /* maximal SMAPS I saw 2 MB     */
50 #define KB(bytes)       ((bytes)/1024)
51
52 #define BYTE_TO_KBYTE(b) ((b) >> 10)
53 #define BYTE_TO_MBYTE(b) ((b) >> 20)
54 #define BYTE_TO_GBYTE(b) ((b) >> 30)
55
56 #define KBYTE_TO_BYTE(k) ((k) << 10)
57 #define KBYTE_TO_MBYTE(k) ((k) >> 10)
58 #define KBYTE_TO_GBYTE(k) ((k) >> 20)
59
60 #define GBYTE_TO_BYTE(g) ((g) << 30)
61 #define GBYTE_TO_KBYTE(g) ((g) << 20)
62 #define GBYTE_TO_MBYTE(g) ((g) << 10)
63
64 typedef struct geminfo geminfo;
65 typedef struct mapinfo mapinfo;
66 typedef struct trib_mapinfo trib_mapinfo;
67
68 enum {
69         OUTPUT_UART,
70         OUTPUT_FILE,
71         NUM_OUTPUT_TYPE
72 };
73
74 struct mapinfo {
75         mapinfo *next;
76         unsigned long start;
77         unsigned long end;
78         unsigned size;
79         unsigned swap;
80         unsigned rss;
81         unsigned pss;
82         unsigned shared_clean;
83         unsigned shared_dirty;
84         unsigned private_clean;
85         unsigned private_dirty;
86         char *perm;
87         char *name;
88 };
89
90 /* classify normal, graphic and other devices memory */
91 struct trib_mapinfo {
92         unsigned shared_clean;
93         unsigned shared_dirty;
94         unsigned private_clean;
95         unsigned private_dirty;
96         unsigned shared_clean_pss;
97         unsigned shared_dirty_pss;
98         unsigned swap;
99         unsigned rss;
100         unsigned pss;
101         unsigned size;
102         unsigned graphic_3d;
103         unsigned gem_rss;
104         unsigned gem_pss;
105         unsigned peak_rss;
106         unsigned other_devices;
107         unsigned gem_mmap;
108 };
109
110 struct geminfo {
111         geminfo *next;
112         unsigned int tgid;
113         unsigned rss_size;
114         unsigned pss_size;
115         unsigned hcount;
116 };
117
118 static int sum;
119 static int verbos;
120 static int use_gpu_mem_info = 0;
121
122 /* reads file contents into memory */
123 static char* cread(const char* path)
124 {
125         /* once allocated area for reads */
126         static char*    text = NULL;
127         static size_t   size = 0;
128
129         ssize_t ret;
130         char*   ptr = text;
131         size_t  cap = size;
132         int     fd  = open(path, O_RDONLY);
133
134         if (fd < 0)
135                 return NULL;
136
137         do {
138                 /* ensure we have enough space */
139                 if (cap == 0) {
140                         ptr = (char*)realloc(text, size + BUF_INC_SIZE);
141                         if (ptr == NULL) {
142                                 ret = -1;
143                                 break;
144                         }
145
146                         text  = ptr;
147                         ptr   = text + size;
148                         cap   = BUF_INC_SIZE;
149                         size += BUF_INC_SIZE;
150                 }
151                 ret = read(fd, ptr, cap);
152                 if (ret == 0) {
153                         *ptr = 0;
154                 } else if (ret > 0) {
155                         cap -= ret;
156                         ptr += ret;
157                 }
158         } while (ret > 0);
159         close(fd);
160
161         return (ret < 0 ? NULL : text);
162 } /* cread */
163
164 /* like fgets/gets but adjusting contents pointer */
165 static inline char* cgets(char** contents)
166 {
167         if (contents && *contents && **contents) {
168                 char* bos = *contents;          /* begin of string */
169                 char* eos = strchr(bos, '\n');  /* end of string   */
170
171                 if (eos) {
172                         *contents = eos + 1;
173                         *eos      = 0;
174                 } else {
175                         *contents = NULL;
176                 }
177                 return bos;
178         }
179
180         return NULL;
181 } /* cgets */
182
183
184 static unsigned get_peak_rss(unsigned int pid)
185 {
186         static const char field[] = "VmHWM:";
187         char tmp[128];
188         char* line;
189         char* value;
190
191         snprintf(tmp, sizeof(tmp), "/proc/%d/status", pid);
192         line = cread(tmp);
193         if (line == NULL) {
194                 fprintf(stderr, "cannot open %s\n", tmp);
195                 return 0;
196         }
197
198         value = strstr(line, field);
199         if (value) {
200                 value += sizeof(field);
201                 return strtoul(value, NULL, 10);
202         }
203
204         return 0;
205 }
206 #define NUM_GEM_FIELD 6
207
208 static geminfo *read_geminfo(FILE *fp)
209 {
210         geminfo *tgeminfo;
211         char line[BUF_MAX];
212         unsigned int pid, tgid, handle, refcount, hcount;
213         unsigned gem_size;
214
215         if (fgets(line, BUF_MAX, fp) != NULL) {
216                 if (sscanf(line, "%d %d %d %d %d 0x%x",
217                         &pid, &tgid, &handle, &refcount,
218                         &hcount, &gem_size) != NUM_GEM_FIELD)
219                         return NULL;
220
221                 if (hcount == 0)
222                         return NULL;
223                 tgeminfo = malloc(sizeof(geminfo));
224                 if (tgeminfo == NULL)
225                         return NULL;
226                 tgeminfo->tgid = tgid;
227                 tgeminfo->hcount = hcount;
228                 tgeminfo->rss_size = KB(gem_size);
229                 tgeminfo->pss_size = KB(gem_size/tgeminfo->hcount);
230         } else
231                 return NULL;
232
233         return tgeminfo;
234 }
235
236
237 static geminfo *find_geminfo(unsigned int tgid, geminfo *gilist)
238 {
239         geminfo *gi;
240         for (gi = gilist; gi; ) {
241                 if (gi->tgid == tgid)
242                         return gi;
243
244                 gi = gi->next;
245         }
246         return NULL;
247 }
248
249 static geminfo *load_geminfo(void)
250 {
251         geminfo *ginfo;
252         geminfo *gilist = NULL;
253         geminfo *exist_ginfo = NULL;
254
255         FILE *drm_fp;
256         char line[BUF_MAX];
257
258         drm_fp = fopen("/sys/kernel/debug/dri/0/gem_info", "r");
259
260         if (drm_fp == NULL) {
261                 fprintf(stderr,
262                 "cannot open /sys/kernel/debug/dri/0/gem_info\n");
263                 return NULL;
264         }
265
266         if (fgets(line, BUF_MAX, drm_fp) == NULL) {
267                 fclose(drm_fp);
268                 return NULL;
269         } else {
270                 /* we should count a number of whitespace separated fields */
271                 int in_field = (line[0] && !isblank(line[0]));
272                 unsigned int size = (unsigned)in_field;
273                 const char*  ptr  = &line[1];
274
275                 /* sscanf() was used in original code, so number of fields */
276                 /* in string is expected to be at least NUM_GEM_FIELD      */
277                 while (*ptr && size < NUM_GEM_FIELD) {
278                         if (isblank(*ptr++)) {
279                                 if (in_field) {
280                                         /* end of field */
281                                         in_field = 0;
282                                 }
283                         } else {
284                                 if (!in_field) {
285                                         /* next field started */
286                                         in_field = 1;
287                                         size++;
288                                 }
289                         }
290                 } /* while */
291
292                 if (size != NUM_GEM_FIELD) {
293                         fclose(drm_fp);
294                         return NULL;
295                 }
296         }
297
298         while ((ginfo = read_geminfo(drm_fp)) != NULL) {
299                 if (gilist && ginfo->tgid == gilist->tgid) {
300                         gilist->pss_size += ginfo->pss_size;
301                         gilist->rss_size += ginfo->rss_size;
302                         free(ginfo);
303                         continue;
304                 } else if (gilist && ((exist_ginfo = find_geminfo(ginfo->tgid, gilist)) != NULL)) {
305                         exist_ginfo->pss_size += ginfo->pss_size;
306                         exist_ginfo->rss_size += ginfo->rss_size;
307                         free(ginfo);
308                         continue;
309                 }
310                 ginfo->next = gilist;
311                 gilist = ginfo;
312         }
313
314         fclose(drm_fp);
315
316         return gilist;
317 }
318
319
320 /* b6e82000-b6e83000 rw-p 00020000 b3:19 714        /usr/lib/ld-2.20-2014.11.so  : TM1
321  * 7f9389d000-7f9389e000 rw-p 0001f000 b3:12 618                            /usr/lib64/ld-2.20-2014.11.so  : TM2
322  * 7fae2e4b2000-7fae2e4b3000 r--p 00021000 fe:01 603                        /usr/lib64/ld-2.20-2014.11.so  : x86-64 Emulator
323  * 01234567890123456789012345678901234567890123456789012345678901234567890123456789
324  * 0         1         2         3         4         5         6         7
325  */
326 mapinfo *read_mapinfo(char** smaps)
327 {
328         char* line;
329         mapinfo *mi;
330         int len;
331         int n;
332
333         if ((line = cgets(smaps)) == 0)
334                 return 0;
335
336         len = strlen(line);
337         if (len < 1)
338                 return 0;
339
340         mi = malloc(sizeof(mapinfo));
341         if (mi == 0)
342                 return 0;
343
344         n = sscanf(line, "%lx-%lx %ms %*s %*s %*s %m[^\n]",
345                            &mi->start, &mi->end, &mi->perm, &mi->name);
346
347         if (n == 3 && !mi->name)
348                 mi->name = strndup("[anon]", strlen("[anon]"));
349         else if (n <= 3) {
350                 fprintf(stderr, "Fail to parse smaps\n");
351                 free(mi);
352                 return 0;
353         }
354
355         while ((line = cgets(smaps))) {
356                 if (sscanf(line, "Size: %d kB", &mi->size) == 1)
357                         ;
358                 else if (sscanf(line, "Rss: %d kB", &mi->rss) == 1)
359                         ;
360                 else if (sscanf(line, "Pss: %d kB", &mi->pss) == 1)
361                         ;
362                 else if (sscanf(line, "Shared_Clean: %d kB", &mi->shared_clean) == 1)
363                         ;
364                 else if (sscanf(line, "Shared_Dirty: %d kB", &mi->shared_dirty) == 1)
365                         ;
366                 else if (sscanf(line, "Private_Clean: %d kB", &mi->private_clean) == 1)
367                         ;
368                 else if (sscanf(line, "Private_Dirty: %d kB", &mi->private_dirty) == 1)
369                         ;
370                 else if (sscanf(line, "Swap: %d kB", &mi->swap) == 1)
371                         ;
372                 if (*smaps) {
373                         /* Drain lines until it meets next VMA address */
374                         char next = **smaps;
375                         if  ((next >= '0' && next <= '9') || (next >= 'a' && next <= 'f'))
376                                 break;
377                 }
378         }
379
380         return mi;
381 }
382
383 static unsigned total_gem_memory(void)
384 {
385         FILE *gem_fp;
386         unsigned total_gem_mem = 0;
387         unsigned name, size, handles, refcount;
388         char line[BUF_MAX];
389
390         gem_fp = fopen("/sys/kernel/debug/dri/0/gem_names", "r");
391         if (gem_fp == NULL) {
392                 fprintf(stderr,
393                 "cannot open /sys/kernel/debug/dri/0/gem_names\n");
394                 return 0;
395         }
396
397         if (fgets(line, BUF_MAX, gem_fp) == NULL) {
398                 fclose(gem_fp);
399                 return 0;
400         }
401
402         while (fgets(line, BUF_MAX, gem_fp) != NULL) {
403                 if (sscanf(line, "%d %d %d %d\n",
404                     &name, &size, &handles, &refcount) == 4) {
405                         if (total_gem_mem <= UINT_MAX - size) {
406                                 total_gem_mem += size;
407                         }
408                 }
409         }
410         fclose(gem_fp);
411
412         return total_gem_mem;
413 }
414
415 int get_zram_used(u_int32_t *zram_used)
416 {
417         FILE *f = NULL;
418         char *fscanf_format = "%u";
419         int ret;
420
421         f = fopen(ZRAM_USED_PATH, "r");
422         if (!f) {
423                 /*
424                  * ZRAM_USED_PATH is deprecated on latest kernel, but to support
425                  * old kernel first try with that and if fails then use new node
426                  * 'mm_stat' later.
427                  */
428                 f = fopen(ZRAM_MM_STAT_PATH, "r");
429                 if (!f) {
430                         fprintf(stderr, "Fail to open zram stat file.\n");
431                         return -1;
432                 }
433                 /* only read 3rd value */
434                 fscanf_format = "%*d %*d %d %*d %*d %*d %*d %*d";
435         }
436
437         ret = fscanf(f, fscanf_format, zram_used);
438         if (ret == EOF) {
439                 fprintf(stderr, "Fail to read file\n");
440                 fclose(f);
441                 return -1;
442         }
443
444         fclose(f);
445         return 0;
446 }
447
448 int fread_uint(const char *path, u_int32_t *number)
449 {
450         FILE *f = NULL;
451         int ret;
452
453         f = fopen(path, "r");
454
455         if (!f) {
456                 fprintf(stderr, "Fail to open %s file.\n", path);
457                 return -1;
458         }
459
460         ret = fscanf(f, "%u", number);
461         if (ret == EOF) {
462                 fprintf(stderr, "Fail to read file\n");
463                 fclose(f);
464                 return -1;
465         }
466
467         fclose(f);
468         return 0;
469 }
470
471 static int cgroup_read_node(const char *cgroup_name,
472                 const char *file_name, unsigned int *value)
473 {
474         char buf[PATH_MAX + NAME_MAX];
475         int ret;
476         snprintf(buf, sizeof(buf), "%s%s", cgroup_name, file_name);
477         ret = fread_uint(buf, value);
478         return ret;
479 }
480
481 /**
482  * @desc Provides usage in bytes for provided memory cgroup. Works
483  * with/without swap accounting.
484  *
485  * @param memcg_path[in] Full path to memory cgroup
486  * @param swap[in] Boolean value for deciding if account usage with swap
487  * @return current cgroup usage in bytes or 0 on error
488  */
489 static unsigned int get_memcg_usage(const char *memcg_path, bool swap)
490 {
491         int ret;
492         unsigned int usage;
493
494         if (swap) {
495                 ret = cgroup_read_node(memcg_path,
496                                 "/memory.memsw.usage_in_bytes", &usage);
497         } else {
498                 ret = cgroup_read_node(memcg_path, "/memory.usage_in_bytes",
499                                 &usage);
500         }
501
502         if (ret != 0)
503                 usage = 0;
504
505         return usage;
506 }
507
508 static void get_memcg_info(FILE *output_fp)
509 {
510         char buf[PATH_MAX];
511         DIR *pdir = NULL;
512         struct dirent *entry;
513         struct stat path_stat;
514         long usage_swap;
515         unsigned long usage, usage_with_swap;
516
517         fprintf(output_fp, "====================================================================\n");
518         fprintf(output_fp, "MEMORY CGROUPS USAGE INFO\n");
519
520         pdir = opendir(MEMCG_PATH);
521         if (pdir == NULL) {
522                 fprintf(stderr, "cannot read directory %s", MEMCG_PATH);
523                 return;
524         }
525
526         errno = 0;
527         while ((entry = readdir(pdir)) != NULL && !errno) {
528                 snprintf(buf, sizeof(buf), "%s/%s", MEMCG_PATH, entry->d_name);
529                 /* If can't stat then ignore */
530                 if (stat(buf, &path_stat) != 0)
531                         continue;
532
533                 /* If it's not directory or it's parent path then ignore */
534                 if (!(S_ISDIR(path_stat.st_mode) &&
535                         strncmp(entry->d_name, "..", 3)))
536                         continue;
537
538                 usage = get_memcg_usage(buf, false);
539                 usage_with_swap = get_memcg_usage(buf, true);
540                 /* It is posible by rounding errors to get negative value */
541                 usage_swap = usage_with_swap - usage;
542                 if (usage_swap < 0)
543                         usage_swap = 0;
544
545                 /* Case of root cgroup in hierarchy */
546                 if (!strncmp(entry->d_name, ".", 2))
547                         fprintf(output_fp, "%13s Mem %3ld MB (%6ld kB), Mem+Swap %3ld MB (%6ld kB), Swap %3ld MB (%6ld kB) \n\n",
548                                 MEMCG_PATH, BYTE_TO_MBYTE(usage),
549                                 BYTE_TO_KBYTE(usage),
550                                 BYTE_TO_MBYTE(usage_with_swap),
551                                 BYTE_TO_KBYTE(usage_with_swap),
552                                 BYTE_TO_MBYTE(usage_swap),
553                                 BYTE_TO_KBYTE(usage_swap));
554                 else
555                         fprintf(output_fp, "memcg: %13s  Mem %3ld MB (%6ld kB), Mem+Swap %3ld MB (%6ld kB), Swap %3ld MB (%6ld kB)\n",
556                                 entry->d_name, BYTE_TO_MBYTE(usage),
557                                 BYTE_TO_KBYTE(usage),
558                                 BYTE_TO_MBYTE(usage_with_swap),
559                                 BYTE_TO_KBYTE(usage_with_swap),
560                                 BYTE_TO_MBYTE(usage_swap),
561                                 BYTE_TO_KBYTE(usage_swap));
562
563         }
564
565         closedir(pdir);
566 }
567
568 static void get_mem_info(FILE *output_fp)
569 {
570         char buf[PATH_MAX];
571         FILE *fp;
572         char *idx;
573         unsigned int free = 0, cached = 0;
574         unsigned int total_mem = 0, available = 0, used;
575         unsigned int swap_total = 0, swap_free = 0, zram_used, swap_used;
576         unsigned int used_ratio;
577
578         if (output_fp == NULL)
579                 return;
580
581         fp = fopen("/proc/meminfo", "r");
582
583         if (!fp) {
584                 fprintf(stderr, "/proc/meminfo open failed, %p", fp);
585                 return;
586         }
587
588         while (fgets(buf, PATH_MAX, fp) != NULL) {
589                 if ((idx = strstr(buf, "MemTotal:"))) {
590                         idx += strlen("Memtotal:");
591                         while ((idx < buf + PATH_MAX) && (*idx < '0' || *idx > '9'))
592                                 idx++;
593                         total_mem = atoi(idx);
594                 } else if ((idx = strstr(buf, "MemFree:"))) {
595                         idx += strlen("MemFree:");
596                         while ((idx < buf + PATH_MAX) && (*idx < '0' || *idx > '9'))
597                                 idx++;
598                         free = atoi(idx);
599                 } else if ((idx = strstr(buf, "MemAvailable:"))) {
600                         idx += strlen("MemAvailable:");
601                         while ((idx < buf + PATH_MAX) && (*idx < '0' || *idx > '9'))
602                                 idx++;
603                         available = atoi(idx);
604                 } else if ((idx = strstr(buf, "Cached:")) && !strstr(buf, "Swap")) {
605                         idx += strlen("Cached:");
606                         while ((idx < buf + PATH_MAX) && (*idx < '0' || *idx > '9'))
607                                 idx++;
608                         cached = atoi(idx);
609                 } else if ((idx = strstr(buf, "SwapTotal:"))) {
610                         idx += strlen("SwapTotal:");
611                         while ((idx < buf + PATH_MAX) && (*idx < '0' || *idx > '9'))
612                                 idx++;
613                         swap_total = atoi(idx);
614                 } else if ((idx = strstr(buf, "SwapFree:"))) {
615                         idx += strlen("SwapFree");
616                         while ((idx < buf + PATH_MAX) && (*idx < '0' || *idx > '9'))
617                                 idx++;
618                         swap_free = atoi(idx);
619                         break;
620                 }
621         }
622
623         if (total_mem == 0) {
624                 fprintf(stderr, "cannot get total memory size\n");
625                 fclose(fp);
626                 return;
627         }
628
629         if (available == 0)
630                 available = free + cached;
631         used = total_mem - available;
632         used_ratio = used * 100 / total_mem;
633         swap_used = swap_total - swap_free;
634
635         if (get_zram_used(&zram_used) < 0)
636                 zram_used = 0;
637
638         fprintf(output_fp,
639                 "====================================================================\n");
640
641
642         fprintf(output_fp, "Total RAM size: \t%15d MB( %6d kB)\n",
643                         total_mem >> 10, total_mem);
644
645         fprintf(output_fp, "Used (Mem+Reclaimable): %15d MB( %6d kB)\n",
646                         (total_mem - free) >> 10, total_mem - free);
647
648         fprintf(output_fp, "Used (Mem+Swap): \t%15d MB( %6d kB)\n",
649                         used >> 10, used);
650
651         fprintf(output_fp, "Used (Mem):  \t\t%15d MB( %6d kB)\n",
652                         used >> 10, used);
653
654         fprintf(output_fp, "Used (Swap): \t\t%15d MB( %6d kB)\n",
655                         swap_used >> 10, swap_used);
656
657         fprintf(output_fp, "Used (Zram block device): %13d MB( %6d kB)\n",
658             BYTE_TO_MBYTE(zram_used), BYTE_TO_KBYTE(zram_used));
659
660         fprintf(output_fp, "Used Ratio: \t\t%15d  %%\n", used_ratio);
661
662         fprintf(output_fp, "Mem Free:\t\t%15d MB( %6d kB)\n",
663                         free >> 10, free);
664
665         fprintf(output_fp, "Available (Free+Reclaimable):%10d MB( %6d kB)\n",
666                         available >> 10,
667                         available);
668         fclose(fp);
669 }
670
671 static int get_tmpfs_info(FILE *output_fp)
672 {
673         FILE *fp;
674         char line[BUF_MAX];
675         char *tmpfs_mp; /* tmpfs mount point */
676         struct statfs tmpfs_info;
677
678         if (output_fp == NULL)
679                 return -1;
680
681         fp = fopen("/etc/mtab", "r");
682         if (fp == NULL)
683                 return -1;
684
685         fprintf(output_fp,
686                 "====================================================================\n");
687         fprintf(output_fp, "TMPFS INFO\n");
688
689         while (fgets(line, BUF_MAX, fp) != NULL) {
690                 if (sscanf(line, "tmpfs %ms tmpfs", &tmpfs_mp) == 1) {
691                         if (statfs(tmpfs_mp, &tmpfs_info) == 0) {
692                                 fprintf(output_fp,
693 #ifndef __USE_FILE_OFFSET64
694                                         "tmpfs %16s  Total %8ld KB, Used %8ld, Avail %8ld\n",
695 #else
696                                         "tmpfs %16s  Total %8lld KB, Used %8lld, Avail %8lld\n",
697 #endif
698                                         tmpfs_mp,
699                                         /* 1 block is 4 KB */
700                                         tmpfs_info.f_blocks * 4,
701                                         (tmpfs_info.f_blocks - tmpfs_info.f_bfree) * 4,
702                                         tmpfs_info.f_bfree * 4);
703                         }
704                         free(tmpfs_mp);
705                         tmpfs_mp = NULL;
706                 }
707         }
708         fclose(fp);
709         return 0;
710 }
711
712 mapinfo *load_maps(int pid)
713 {
714         char* smaps;
715         char tmp[128];
716         mapinfo *milist = 0;
717         mapinfo *mi;
718
719         snprintf(tmp, sizeof(tmp), "/proc/%d/smaps", pid);
720         smaps = cread(tmp);
721         if (smaps == NULL)
722                 return 0;
723
724         while ((mi = read_mapinfo(&smaps)) != 0) {
725                 if (milist) {
726                         if ((!strcmp(mi->name, milist->name)
727                              && (mi->name[0] != '['))) {
728                                 milist->size += mi->size;
729                                 milist->swap += mi->swap;
730                                 milist->rss += mi->rss;
731                                 milist->pss += mi->pss;
732                                 milist->shared_clean += mi->shared_clean;
733                                 milist->shared_dirty += mi->shared_dirty;
734                                 milist->private_clean += mi->private_clean;
735                                 milist->private_dirty += mi->private_dirty;
736
737                                 milist->end = mi->end;
738                                 strncpy(milist->perm, mi->perm, 4);
739                                 free(mi->perm);
740                                 free(mi->name);
741                                 free(mi);
742                                 continue;
743                         }
744                 }
745                 mi->next = milist;
746                 milist = mi;
747         }
748
749         return milist;
750 }
751
752 static void init_trib_mapinfo(trib_mapinfo *tmi)
753 {
754         if (!tmi)
755                 return;
756         tmi->shared_clean = 0;
757         tmi->shared_dirty = 0;
758         tmi->private_clean = 0;
759         tmi->private_dirty = 0;
760         tmi->swap = 0;
761         tmi->shared_clean_pss = 0;
762         tmi->shared_dirty_pss = 0;
763         tmi->rss = 0;
764         tmi->pss = 0;
765         tmi->size = 0;
766         tmi->graphic_3d = 0;
767         tmi->gem_rss = 0;
768         tmi->gem_pss = 0;
769         tmi->peak_rss = 0;
770         tmi->other_devices = 0;
771         tmi->gem_mmap = 0;
772 }
773
774 unsigned int get_graphic_3d_meminfo(unsigned int tgid)
775 {
776         char command[256], buf[256];
777         char *tmp[2];
778         unsigned int size = 0;
779         int tid, ret;
780         FILE *p_gpu;
781
782         snprintf(command, sizeof(command), "%s %d", STR_3D_UNIFIED_PATH, tgid);
783         p_gpu = popen(command, "r");
784         if (p_gpu) {
785                 if (fgets(buf, 256, p_gpu)) {
786                         ret = sscanf(buf, "%d %ms %d %ms", &tid, &tmp[0], &size, &tmp[1]);
787                         if (ret == 4) {
788                                 free(tmp[0]);
789                                 free(tmp[1]);
790                         }
791                 }
792                 pclose(p_gpu);
793         }
794         return size;
795 }
796
797 static int
798 get_trib_mapinfo(unsigned int tgid, mapinfo *milist,
799                  geminfo *gilist, trib_mapinfo *result)
800
801 {
802         mapinfo *mi;
803         mapinfo *temp = NULL;
804         geminfo *gi;
805
806         if (!result)
807                 return -EINVAL;
808
809         init_trib_mapinfo(result);
810
811         if (use_gpu_mem_info)
812                 result->graphic_3d = get_graphic_3d_meminfo(tgid);
813
814         for (mi = milist; mi;) {
815                 if (!use_gpu_mem_info && strstr(mi->name, STR_SGX_PATH)) {
816                         result->graphic_3d += mi->pss;
817                 } else if (!use_gpu_mem_info && (strstr(mi->name, STR_3D_PATH1) ||
818                         strstr(mi->name, STR_3D_PATH2))) {
819                         result->graphic_3d += mi->size;
820                 } else if (mi->rss != 0 && mi->pss == 0
821                            && mi->shared_clean == 0
822                            && mi->shared_dirty == 0
823                            && mi->private_clean == 0
824                            && mi->private_dirty == 0
825                            && mi->swap == 0) {
826                         result->other_devices += mi->size;
827                 } else if (!strncmp(mi->name, STR_DRM_PATH1,
828                                 sizeof(STR_DRM_PATH1)) ||
829                                 !strncmp(mi->name, STR_DRM_PATH2,
830                                 sizeof(STR_DRM_PATH2))) {
831                         result->gem_mmap += mi->rss;
832                 } else {
833                         result->shared_clean += mi->shared_clean;
834                         result->shared_dirty += mi->shared_dirty;
835                         result->private_clean += mi->private_clean;
836                         result->private_dirty += mi->private_dirty;
837                         result->swap += mi->swap;
838                         result->rss += mi->rss;
839                         result->pss += mi->pss;
840                         result->size += mi->size;
841
842                         if (mi->shared_clean != 0)
843                                 result->shared_clean_pss += mi->pss;
844                         else if (mi->shared_dirty != 0)
845                                 result->shared_dirty_pss += mi->pss;
846                 }
847
848                 temp = mi;
849                 mi = mi->next;
850                 free(temp->perm);
851                 free(temp->name);
852                 free(temp);
853                 temp = NULL;
854         }
855
856         result->peak_rss = get_peak_rss(tgid);
857         if (result->peak_rss < result->rss)
858                 result->peak_rss = result->rss;
859         if (result->gem_mmap > 0)
860                 result->peak_rss -= result->gem_mmap;
861
862         gi = find_geminfo(tgid, gilist);
863         if (gi != NULL) {
864                 result->gem_rss = gi->rss_size;
865                 result->gem_pss = gi->pss_size;
866         }
867
868         return 0;
869 }
870
871 static int get_cmdline(unsigned int pid, char *cmdline)
872 {
873         FILE *fp;
874         char buf[NAME_MAX] = {0, };
875         int ret = -1;
876
877         snprintf(buf, sizeof(buf), "/proc/%d/cmdline", pid);
878         fp = fopen(buf, "r");
879         if (fp == 0) {
880                 if (errno == ENOENT)
881                         errno = 0;
882                 fprintf(stderr, "cannot file open %s\n", buf);
883                 return ret;
884         }
885         if ((ret = fscanf(fp, "%4095s", cmdline)) < 1) {
886                 fclose(fp);
887                 return ret;
888         }
889         fclose(fp);
890
891         return ret;
892 }
893
894 static int get_oomscoreadj(unsigned int pid, const char *oom_string)
895 {
896         FILE *fp;
897         char tmp[256];
898         int oomadj_val;
899
900         snprintf(tmp, sizeof(tmp), "/proc/%d/%s", pid, oom_string);
901         fp = fopen(tmp, "r");
902
903         if (fp == NULL) {
904                 oomadj_val = -50;
905                 return oomadj_val;
906         }
907         if (fgets(tmp, sizeof(tmp), fp) == NULL) {
908                 oomadj_val = -100;
909                 fclose(fp);
910                 return oomadj_val;
911         }
912
913         oomadj_val = atoi(tmp);
914
915         fclose(fp);
916         return oomadj_val;
917 }
918
919 static void get_rss(pid_t pid, unsigned long *result)
920 {
921         FILE *fp;
922         char proc_path[PATH_MAX];
923         unsigned long rss = 0;
924
925         *result = 0;
926
927         snprintf(proc_path, sizeof(proc_path), "/proc/%d/statm", pid);
928         fp = fopen(proc_path, "r");
929         if (fp == NULL)
930                 return;
931
932         if (fscanf(fp, "%*s %ld", &rss) < 1) {
933                 fclose(fp);
934                 return;
935         }
936
937         fclose(fp);
938
939         /* convert page to Kb */
940         *result = rss << 2;
941         return;
942 }
943
944 static const char* get_readable_oom_path(void)
945 {
946         FILE *fp = NULL;
947         char tmp[256];
948
949         snprintf(tmp, sizeof(tmp), "/proc/1/%s", OOM_SCORE_ADJ_STR);
950         fp = fopen(tmp, "r");
951         if (fp) {
952                 fclose(fp);
953                 return OOM_SCORE_ADJ_STR;
954         }
955         snprintf(tmp, sizeof(tmp), "/proc/1/%s", OOM_SCORE_STR);
956         fp = fopen(tmp, "r");
957         if (fp) {
958                 fclose(fp);
959                 return OOM_SCORE_STR;
960         }
961         return NULL;
962 }
963
964 static void show_rss(int output_type, char *output_path)
965 {
966         DIR *pDir = NULL;
967         struct dirent *curdir;
968         pid_t pid;
969         char cmdline[PATH_MAX];
970         FILE *output_file = NULL;
971         int oom_score_adj;
972         unsigned long rss;
973         const char *oom_path = NULL;
974
975         oom_path = get_readable_oom_path();
976         if (!oom_path) {
977                 fprintf(stderr, "there is no readable oom path\n");
978                 return;
979         }
980
981         pDir = opendir("/proc");
982         if (pDir == NULL) {
983                 fprintf(stderr, "cannot read directory /proc.\n");
984                 return;
985         }
986
987         if (output_type == OUTPUT_FILE && output_path) {
988                 output_file = fopen(output_path, "w+");
989                 if (!output_file) {
990                         fprintf(stderr, "cannot open output file(%s)\n",
991                                 output_path);
992                         closedir(pDir);
993                         exit(1);
994                 }
995         } else
996                 output_file = stdout;
997
998
999         fprintf(output_file,
1000                         "     PID      RSS %14s    COMMAND\n", oom_path);
1001
1002         errno = 0;
1003         while ((curdir = readdir(pDir)) != NULL && !errno) {
1004                 pid = atoi(curdir->d_name);
1005                 if (pid < 1 || pid > 32768 || pid == getpid())
1006                         continue;
1007
1008                 if (get_cmdline(pid, cmdline) < 0)
1009                         continue;
1010                 get_rss(pid, &rss);
1011                 oom_score_adj = get_oomscoreadj(pid, oom_path);
1012
1013                 fprintf(output_file,
1014                                 "%8d %8lu       %8d    %s\n",
1015                                 pid,
1016                                 rss,
1017                                 oom_score_adj,
1018                                 cmdline);
1019
1020
1021         } /* end of while */
1022
1023         get_tmpfs_info(output_file);
1024         get_mem_info(output_file);
1025
1026         fclose(output_file);
1027         closedir(pDir);
1028
1029         return;
1030 }
1031
1032 static int show_map_all_new(int output_type, char *output_path)
1033 {
1034         DIR *pDir = NULL;
1035         struct dirent *curdir;
1036         unsigned int pid;
1037         mapinfo *milist;
1038         geminfo *glist;
1039         unsigned total_pss = 0;
1040         unsigned total_private = 0;
1041         unsigned total_private_code = 0;
1042         unsigned total_private_data = 0;
1043         unsigned total_shared_code = 0;
1044         unsigned total_shared_data = 0;
1045         unsigned total_shared_code_pss = 0;
1046         unsigned total_shared_data_pss = 0;
1047         unsigned total_swap = 0;
1048         unsigned total_rss = 0;
1049         unsigned total_graphic_3d = 0;
1050         unsigned total_gem_rss = 0;
1051         unsigned total_gem_pss = 0;
1052         unsigned total_peak_rss = 0;
1053         unsigned total_allocated_gem = 0;
1054         trib_mapinfo tmi;
1055         char cmdline[PATH_MAX];
1056         FILE *output_file = NULL;
1057         int oom_score_adj;
1058         const char *oom_path = NULL;
1059
1060         oom_path = get_readable_oom_path();
1061         if (!oom_path) {
1062                 fprintf(stderr, "there is no readable oom path\n");
1063                 return 0;
1064         }
1065
1066         use_gpu_mem_info = (!access(STR_3D_UNIFIED_PATH, F_OK | X_OK)) ? 1 : 0;
1067         errno = 0;
1068
1069         pDir = opendir("/proc");
1070         if (pDir == NULL) {
1071                 fprintf(stderr, "cannot read directory /proc.\n");
1072                 return 0;
1073         }
1074
1075         if (output_type == OUTPUT_FILE && output_path) {
1076                 output_file = fopen(output_path, "w+");
1077                 if (!output_file) {
1078                         fprintf(stderr, "cannot open output file(%s)\n",
1079                                 output_path);
1080                         closedir(pDir);
1081                         exit(1);
1082                 }
1083         } else
1084                 output_file = stdout;
1085
1086         glist = load_geminfo();
1087
1088         if (!sum) {
1089                 if (verbos)
1090                         fprintf(output_file,
1091                                         "     PID  S(CODE)  S(DATA)  P(CODE)  P(DATA) "
1092                                         "    PEAK      PSS       3D GEM(PSS) GEM(RSS) "
1093                                         "    SWAP %14s    COMMAND\n", oom_path);
1094                 else
1095                         fprintf(output_file,
1096                                         "     PID     CODE     DATA     PEAK      PSS "
1097                                         "      3D GEM(PSS)     SWAP    COMMAND\n");
1098         }
1099
1100         errno = 0;
1101         while ((curdir = readdir(pDir)) != NULL && !errno) {
1102                 pid = atoi(curdir->d_name);
1103                 if (pid < 1 || pid > 32768 || pid == getpid())
1104                         continue;
1105
1106                 if (get_cmdline(pid, cmdline) < 0)
1107                         continue;
1108
1109                 milist = load_maps(pid);
1110                 if (milist == 0)
1111                         continue;
1112
1113                 /* get classified map info */
1114                 get_trib_mapinfo(pid, milist, glist, &tmi);
1115                 oom_score_adj = get_oomscoreadj(pid, oom_path);
1116
1117                 if (!sum) {
1118                         if (verbos)
1119                                 fprintf(output_file,
1120                                         "%8d %8d %8d %8d %8d "
1121                                         "%8d %8d %8d %8d %8d "
1122                                         "%8d       %8d    %s\n",
1123                                         pid,
1124                                         tmi.shared_clean,
1125                                         tmi.shared_dirty,
1126                                         tmi.private_clean,
1127                                         tmi.private_dirty,
1128                                         tmi.peak_rss,
1129                                         tmi.pss,
1130                                         tmi.graphic_3d,
1131                                         tmi.gem_pss,
1132                                         tmi.gem_rss,
1133                                         tmi.swap,
1134                                         oom_score_adj,
1135                                         cmdline);
1136                         else
1137                                 fprintf(output_file,
1138                                         "%8d %8d %8d %8d %8d "
1139                                         "%8d %8d %8d    %s\n",
1140                                         pid,
1141                                         tmi.shared_clean + tmi.private_clean,
1142                                         tmi.shared_dirty + tmi.private_dirty,
1143                                         tmi.peak_rss,
1144                                         tmi.pss,
1145                                         tmi.graphic_3d,
1146                                         tmi.gem_pss,
1147                                         tmi.swap,
1148                                         cmdline);
1149
1150                         if (tmi.other_devices != 0)
1151                                 fprintf(output_file,
1152                                         "%s(%d) %d KB may mapped by device(s).\n",
1153                                         cmdline, pid, tmi.other_devices);
1154                 }
1155
1156                 total_private += (tmi.private_clean + tmi.private_dirty);
1157                 total_pss += tmi.pss;
1158                 total_rss += tmi.rss;
1159                 total_graphic_3d += tmi.graphic_3d;
1160                 total_gem_rss += tmi.gem_rss;
1161                 total_gem_pss += tmi.gem_pss;
1162                 total_private_code += tmi.private_clean;
1163                 total_private_data += tmi.private_dirty;
1164                 total_swap += tmi.swap;
1165                 total_shared_code += tmi.shared_clean;
1166                 total_shared_data += tmi.shared_dirty;
1167                 total_peak_rss += tmi.peak_rss;
1168
1169                 total_shared_code_pss += tmi.shared_clean_pss;
1170                 total_shared_data_pss += tmi.shared_dirty_pss;
1171
1172         } /* end of while */
1173
1174         total_allocated_gem = KB(total_gem_memory());
1175         fprintf(output_file,
1176                         "==============================================="
1177                         "===============================================\n");
1178         if (verbos) {
1179                 fprintf(output_file,
1180                                 "TOTAL:    S(CODE)  S(DATA)  P(CODE)  P(DATA) "
1181                                 "    PEAK      PSS       3D GEM(PSS) GEM(RSS) "
1182                                 "GEM(ALLOC)     SWAP  TOTAL(KB)\n");
1183                 fprintf(output_file,
1184                         "         %8d %8d %8d %8d "
1185                         "%8d %8d %8d %8d %8d "
1186                         "%10d %8d %10d\n",
1187                         total_shared_code, total_shared_data,
1188                         total_private_code, total_private_data,
1189                         total_peak_rss, total_pss, total_graphic_3d,
1190                         total_gem_pss, total_gem_rss,
1191                         total_allocated_gem, total_swap,
1192                         total_pss + total_graphic_3d +
1193                         total_allocated_gem);
1194         } else {
1195                 fprintf(output_file,
1196                         "TOTAL:       CODE     DATA     PEAK      PSS "
1197                         "      3D GEM(PSS) GEM(ALLOC)     SWAP  TOTAL(KB)\n");
1198                 fprintf(output_file,
1199                         "         %8d %8d %8d %8d "
1200                         "%8d %8d %10d %8d %10d\n",
1201                         total_shared_code + total_private_code,
1202                         total_shared_data + total_private_data,
1203                         total_peak_rss, total_pss,
1204                         total_graphic_3d, total_gem_pss,
1205                         total_allocated_gem, total_swap,
1206                         total_pss + total_graphic_3d +
1207                         total_allocated_gem);
1208
1209         }
1210
1211         if (verbos)
1212                 fprintf(output_file,
1213                         "* S(CODE): shared clean memory, it includes"
1214                         " duplicated memory\n"
1215                         "* S(DATA): shared dirty memory, it includes"
1216                         " duplicated memory\n"
1217                         "* P(CODE): private clean memory\n"
1218                         "* P(DATA): private dirty memory\n"
1219                         "* PEAK: peak memory usage of S(CODE) + S(DATA)"
1220                         " + P(CODE) + P(DATA)\n"
1221                         "* PSS: Proportional Set Size\n"
1222                         "* 3D: memory allocated by GPU driver\n"
1223                         "* GEM(PSS): GEM memory divided by # of sharers\n"
1224                         "* GEM(RSS): GEM memory including duplicated memory\n"
1225                         "* GEM(ALLOC): sum of unique gem memory in the system\n"
1226                         "* TOTAL: PSS + 3D + GEM(ALLOC)\n");
1227         else
1228                 fprintf(output_file,
1229                         "* CODE: shared and private clean memory\n"
1230                         "* DATA: shared and private dirty memory\n"
1231                         "* PEAK: peak memory usage of CODE + DATA\n"
1232                         "* PSS: Proportional Set Size\n"
1233                         "* 3D: memory allocated by GPU driver\n"
1234                         "* GEM(PSS): GEM memory divided by # of sharers\n"
1235                         "* GEM(ALLOC): sum of unique GEM memory in the system\n"
1236                         "* TOTAL: PSS + 3D + GEM(ALLOC)\n");
1237
1238         get_tmpfs_info(output_file);
1239         get_memcg_info(output_file);
1240         get_mem_info(output_file);
1241
1242         fclose(output_file);
1243         if (glist)
1244                 free(glist);
1245         closedir(pDir);
1246         return 1;
1247 }
1248
1249 static int show_map_new(int pid)
1250 {
1251         mapinfo *milist;
1252         mapinfo *mi;
1253         mapinfo *temp = NULL;
1254         unsigned shared_dirty = 0;
1255         unsigned shared_clean = 0;
1256         unsigned private_dirty = 0;
1257         unsigned private_clean = 0;
1258         unsigned pss = 0;
1259         unsigned long start = 0;
1260         unsigned long end = 0;
1261         unsigned private_clean_total = 0;
1262         unsigned private_dirty_total = 0;
1263         unsigned shared_clean_total = 0;
1264         unsigned shared_dirty_total = 0;
1265         int duplication = 0;
1266
1267         milist = load_maps(pid);
1268
1269         if (milist == 0) {
1270                 fprintf(stderr, "cannot get /proc/smaps for pid %d\n", pid);
1271                 return 1;
1272         }
1273
1274         if (!sum) {
1275                 printf(" S(CODE)  S(DATA)  P(CODE)  P(DATA)      PSS "
1276                         "ADDR(start-end)   OBJECT NAME\n");
1277                 printf("-------- -------- -------- -------- -------- "
1278                         "----------------- ------------------------------\n");
1279         } else {
1280                 printf(" S(CODE)  S(DATA)  P(CODE)  P(DATA) "
1281                         "               PSS\n");
1282                 printf("-------- -------- -------- -------- "
1283                         "------------------\n");
1284         }
1285         for (mi = milist; mi;) {
1286                 shared_clean += mi->shared_clean;
1287                 shared_dirty += mi->shared_dirty;
1288                 private_clean += mi->private_clean;
1289                 private_dirty += mi->private_dirty;
1290                 pss += mi->pss;
1291
1292                 shared_clean_total += mi->shared_clean;
1293                 shared_dirty_total += mi->shared_dirty;
1294                 private_clean_total += mi->private_clean;
1295                 private_dirty_total += mi->private_dirty;
1296
1297                 if (!duplication)
1298                         start = mi->start;
1299
1300                 if ((mi->next && !strcmp(mi->next->name, mi->name)) &&
1301                     (mi->next->start == mi->end)) {
1302                         duplication = 1;
1303
1304                         temp = mi;
1305                         mi = mi->next;
1306                         free(temp->perm);
1307                         free(temp->name);
1308                         free(temp);
1309                         temp = NULL;
1310                         continue;
1311                 }
1312                 end = mi->end;
1313                 duplication = 0;
1314
1315                 if (!sum) {
1316                         printf("%8d %8d %8d %8d %8d %08lx-%08lx %s\n",
1317                                 shared_clean, shared_dirty, private_clean,
1318                                 private_dirty, mi->pss, start, end, mi->name);
1319                 }
1320                 shared_clean = 0;
1321                 shared_dirty = 0;
1322                 private_clean = 0;
1323                 private_dirty = 0;
1324
1325                 temp = mi;
1326                 mi = mi->next;
1327                 free(temp->perm);
1328                 free(temp->name);
1329                 free(temp);
1330                 temp = NULL;
1331         }
1332         if (sum) {
1333                 printf("%8d %8d %8d %8d %18d\n",
1334                        shared_clean_total,
1335                        shared_dirty_total,
1336                        private_clean_total,
1337                        private_dirty_total,
1338                        pss);
1339         }
1340
1341         return 1;
1342 }
1343
1344 int main(int argc, char *argv[])
1345 {
1346         int usage = 1;
1347         sum = 0;
1348
1349         if (argc > 1) {
1350                 if (!strncmp(argv[1], "-r", strlen("-r")+1)) {
1351                         if (argc >= 3)
1352                                 show_rss(OUTPUT_FILE, argv[2]);
1353                         else
1354                                 show_rss(OUTPUT_UART, NULL);
1355                         usage = 0;
1356                 } else if (!strncmp(argv[1], "-s", strlen("-s")+1)) {
1357                         sum = 1;
1358                         if (argc == 3 && atoi(argv[2]) > 0) {
1359                                 show_map_new(atoi(argv[2]));
1360                                 usage = 0;
1361                         }
1362                 } else if (!strncmp(argv[1], "-a", strlen("-a")+1)) {
1363                         verbos = 0;
1364                         show_map_all_new(OUTPUT_UART, NULL);
1365                         usage = 0;
1366                 } else if (!strncmp(argv[1], "-v", strlen("-v")+1)) {
1367                         verbos = 1;
1368                         show_map_all_new(OUTPUT_UART, NULL);
1369                         usage = 0;
1370                 } else if (!strncmp(argv[1], "-f", strlen("-f")+1)) {
1371                         if (argc >= 3) {
1372                                 verbos = 1;
1373                                 show_map_all_new(OUTPUT_FILE, argv[2]);
1374                                 usage = 0;
1375                         }
1376                 } else if (argc == 2 && atoi(argv[1]) > 0) {
1377                         show_map_new(atoi(argv[1]));
1378                         usage = 0;
1379                 }
1380         }
1381         if (usage) {
1382                 fprintf(stderr,
1383                         "memps [-a] | [-v] | [-r] | [-s] <pid> | <pid> | [-f] <output file full path>\n"
1384                         "        -s = sum (show only sum of each)\n"
1385                         "        -f = all (show all processes via output file)\n"
1386                         "        -a = all (show all processes)\n"
1387                         "        -r = all (show rss of all processes)\n"
1388                         "        -v = verbos (show all processes in detail)\n");
1389         }
1390
1391         return 0;
1392 }