| /* |
| * Copyright 2018 Google, Inc |
| * |
| * 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 <assert.h> |
| #include <errno.h> |
| #include <log/log.h> |
| #include <log/log_id.h> |
| #include <statslog.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/uio.h> |
| #include <time.h> |
| |
| #ifdef LMKD_LOG_STATS |
| |
| #define LINE_MAX 128 |
| #define STRINGIFY(x) STRINGIFY_INTERNAL(x) |
| #define STRINGIFY_INTERNAL(x) #x |
| |
| static bool enable_stats_log = property_get_bool("ro.lmk.log_stats", false); |
| |
| struct proc { |
| int pid; |
| char taskname[LINE_MAX]; |
| struct proc* pidhash_next; |
| }; |
| |
| #define PIDHASH_SZ 1024 |
| static struct proc** pidhash = NULL; |
| #define pid_hashfn(x) ((((x) >> 8) ^ (x)) & (PIDHASH_SZ - 1)) |
| |
| /** |
| * Logs the change in LMKD state which is used as start/stop boundaries for logging |
| * LMK_KILL_OCCURRED event. |
| * Code: LMK_STATE_CHANGED = 54 |
| */ |
| int |
| stats_write_lmk_state_changed(int32_t state) { |
| if (enable_stats_log) { |
| return android::lmkd::stats::stats_write(android::lmkd::stats::LMK_STATE_CHANGED, state); |
| } else { |
| return -EINVAL; |
| } |
| } |
| |
| static struct proc* pid_lookup(int pid) { |
| struct proc* procp; |
| |
| if (!pidhash) return NULL; |
| |
| for (procp = pidhash[pid_hashfn(pid)]; procp && procp->pid != pid; procp = procp->pidhash_next) |
| ; |
| |
| return procp; |
| } |
| |
| /** |
| * Logs the event when LMKD kills a process to reduce memory pressure. |
| * Code: LMK_KILL_OCCURRED = 51 |
| */ |
| int |
| stats_write_lmk_kill_occurred(int32_t uid, char const* process_name, |
| int32_t oom_score, int32_t min_oom_score, int tasksize, |
| struct memory_stat *mem_st) { |
| if (enable_stats_log) { |
| return android::lmkd::stats::stats_write( |
| android::lmkd::stats::LMK_KILL_OCCURRED, |
| uid, |
| process_name, |
| oom_score, |
| mem_st ? mem_st->pgfault : -1, |
| mem_st ? mem_st->pgmajfault : -1, |
| mem_st ? mem_st->rss_in_bytes : tasksize * BYTES_IN_KILOBYTE, |
| mem_st ? mem_st->cache_in_bytes : -1, |
| mem_st ? mem_st->swap_in_bytes : -1, |
| mem_st ? mem_st->process_start_time_ns : -1, |
| min_oom_score |
| ); |
| } else { |
| return -EINVAL; |
| } |
| } |
| |
| int stats_write_lmk_kill_occurred_pid(int32_t uid, int pid, int32_t oom_score, |
| int32_t min_oom_score, int tasksize, |
| struct memory_stat* mem_st) { |
| struct proc* proc = pid_lookup(pid); |
| if (!proc) return -EINVAL; |
| |
| return stats_write_lmk_kill_occurred(uid, proc->taskname, oom_score, min_oom_score, |
| tasksize, mem_st); |
| } |
| |
| static void memory_stat_parse_line(char* line, struct memory_stat* mem_st) { |
| char key[LINE_MAX + 1]; |
| int64_t value; |
| |
| sscanf(line, "%" STRINGIFY(LINE_MAX) "s %" SCNd64 "", key, &value); |
| |
| if (strcmp(key, "total_") < 0) { |
| return; |
| } |
| |
| if (!strcmp(key, "total_pgfault")) |
| mem_st->pgfault = value; |
| else if (!strcmp(key, "total_pgmajfault")) |
| mem_st->pgmajfault = value; |
| else if (!strcmp(key, "total_rss")) |
| mem_st->rss_in_bytes = value; |
| else if (!strcmp(key, "total_cache")) |
| mem_st->cache_in_bytes = value; |
| else if (!strcmp(key, "total_swap")) |
| mem_st->swap_in_bytes = value; |
| } |
| |
| static int memory_stat_from_cgroup(struct memory_stat* mem_st, int pid, uid_t uid) { |
| FILE *fp; |
| char buf[PATH_MAX]; |
| |
| snprintf(buf, sizeof(buf), MEMCG_PROCESS_MEMORY_STAT_PATH, uid, pid); |
| |
| fp = fopen(buf, "r"); |
| |
| if (fp == NULL) { |
| return -1; |
| } |
| |
| while (fgets(buf, PAGE_SIZE, fp) != NULL) { |
| memory_stat_parse_line(buf, mem_st); |
| } |
| fclose(fp); |
| |
| return 0; |
| } |
| |
| static int memory_stat_from_procfs(struct memory_stat* mem_st, int pid) { |
| char path[PATH_MAX]; |
| char buffer[PROC_STAT_BUFFER_SIZE]; |
| int fd, ret; |
| |
| snprintf(path, sizeof(path), PROC_STAT_FILE_PATH, pid); |
| if ((fd = open(path, O_RDONLY | O_CLOEXEC)) < 0) { |
| return -1; |
| } |
| |
| ret = read(fd, buffer, sizeof(buffer)); |
| if (ret < 0) { |
| close(fd); |
| return -1; |
| } |
| close(fd); |
| |
| // field 10 is pgfault |
| // field 12 is pgmajfault |
| // field 22 is starttime |
| // field 24 is rss_in_pages |
| int64_t pgfault = 0, pgmajfault = 0, starttime = 0, rss_in_pages = 0; |
| if (sscanf(buffer, |
| "%*u %*s %*s %*d %*d %*d %*d %*d %*d %" SCNd64 " %*d " |
| "%" SCNd64 " %*d %*u %*u %*d %*d %*d %*d %*d %*d " |
| "%" SCNd64 " %*d %" SCNd64 "", |
| &pgfault, &pgmajfault, &starttime, &rss_in_pages) != 4) { |
| return -1; |
| } |
| mem_st->pgfault = pgfault; |
| mem_st->pgmajfault = pgmajfault; |
| mem_st->rss_in_bytes = (rss_in_pages * PAGE_SIZE); |
| mem_st->process_start_time_ns = starttime * (NS_PER_SEC / sysconf(_SC_CLK_TCK)); |
| |
| return 0; |
| } |
| |
| struct memory_stat *stats_read_memory_stat(bool per_app_memcg, int pid, uid_t uid) { |
| static struct memory_stat mem_st = {}; |
| |
| if (!enable_stats_log) { |
| return NULL; |
| } |
| |
| if (per_app_memcg) { |
| if (memory_stat_from_cgroup(&mem_st, pid, uid) == 0) { |
| return &mem_st; |
| } |
| } else { |
| if (memory_stat_from_procfs(&mem_st, pid) == 0) { |
| return &mem_st; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void proc_insert(struct proc* procp) { |
| if (!pidhash) { |
| pidhash = static_cast<struct proc**>(calloc(PIDHASH_SZ, sizeof(*pidhash))); |
| } |
| |
| int hval = pid_hashfn(procp->pid); |
| procp->pidhash_next = pidhash[hval]; |
| pidhash[hval] = procp; |
| } |
| |
| void stats_remove_taskname(int pid) { |
| if (!enable_stats_log || !pidhash) { |
| return; |
| } |
| |
| int hval = pid_hashfn(pid); |
| struct proc* procp; |
| struct proc* prevp; |
| |
| for (procp = pidhash[hval], prevp = NULL; procp && procp->pid != pid; |
| procp = procp->pidhash_next) |
| prevp = procp; |
| |
| if (!procp) |
| return; |
| |
| if (!prevp) |
| pidhash[hval] = procp->pidhash_next; |
| else |
| prevp->pidhash_next = procp->pidhash_next; |
| |
| free(procp); |
| } |
| |
| void stats_store_taskname(int pid, const char* taskname) { |
| if (!enable_stats_log) { |
| return; |
| } |
| |
| struct proc* procp = pid_lookup(pid); |
| if (procp != NULL) { |
| if (strcmp(procp->taskname, taskname) == 0) { |
| return; |
| } |
| stats_remove_taskname(pid); |
| } |
| procp = static_cast<struct proc*>(malloc(sizeof(struct proc))); |
| procp->pid = pid; |
| strncpy(procp->taskname, taskname, LINE_MAX - 1); |
| procp->taskname[LINE_MAX - 1] = '\0'; |
| proc_insert(procp); |
| } |
| |
| void stats_purge_tasknames() { |
| if (!enable_stats_log || !pidhash) { |
| return; |
| } |
| |
| struct proc* procp; |
| struct proc* next; |
| int i; |
| for (i = 0; i < PIDHASH_SZ; i++) { |
| procp = pidhash[i]; |
| while (procp) { |
| next = procp->pidhash_next; |
| free(procp); |
| procp = next; |
| } |
| } |
| memset(pidhash, 0, PIDHASH_SZ * sizeof(*pidhash)); |
| } |
| |
| #endif /* LMKD_LOG_STATS */ |