You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
346 lines
9.8 KiB
346 lines
9.8 KiB
/*
|
|
* 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 <fcntl.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/endian.h>
|
|
#include <sys/param.h>
|
|
#include <sys/uio.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef LMKD_LOG_STATS
|
|
|
|
#define STRINGIFY(x) STRINGIFY_INTERNAL(x)
|
|
#define STRINGIFY_INTERNAL(x) #x
|
|
|
|
/**
|
|
* Used to make sure that the payload is always smaller than LMKD_REPLY_MAX_SIZE
|
|
*/
|
|
#define BUILD_BUG_ON(cond) ((void)sizeof(char[1 - 2 * !!(cond)]))
|
|
|
|
static bool enable_stats_log = property_get_bool("ro.lmk.log_stats", true);
|
|
|
|
struct proc {
|
|
int pid;
|
|
char taskname[MAX_TASKNAME_LEN];
|
|
struct proc* pidhash_next;
|
|
};
|
|
|
|
#define PIDHASH_SZ 1024
|
|
static struct proc** pidhash = NULL;
|
|
#define pid_hashfn(x) ((((x) >> 8) ^ (x)) & (PIDHASH_SZ - 1))
|
|
|
|
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;
|
|
}
|
|
|
|
static void memory_stat_parse_line(char* line, struct memory_stat* mem_st) {
|
|
char key[MAX_TASKNAME_LEN + 1];
|
|
int64_t value;
|
|
|
|
sscanf(line, "%" STRINGIFY(MAX_TASKNAME_LEN) "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
|
|
int64_t pgfault = 0, pgmajfault = 0, starttime = 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 "",
|
|
&pgfault, &pgmajfault, &starttime) != 3) {
|
|
return -1;
|
|
}
|
|
mem_st->pgfault = pgfault;
|
|
mem_st->pgmajfault = pgmajfault;
|
|
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,
|
|
int64_t rss_bytes, int64_t swap_bytes) {
|
|
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) {
|
|
mem_st.rss_in_bytes = rss_bytes;
|
|
mem_st.swap_in_bytes = swap_bytes;
|
|
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 || !taskname) {
|
|
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, MAX_TASKNAME_LEN - 1);
|
|
procp->taskname[MAX_TASKNAME_LEN - 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));
|
|
}
|
|
|
|
const char* stats_get_task_name(int pid) {
|
|
struct proc* proc = pid_lookup(pid);
|
|
return proc ? proc->taskname : NULL;
|
|
}
|
|
|
|
/**
|
|
* Writes int32 in a machine independent way
|
|
* https://docs.oracle.com/javase/7/docs/api/java/io/DataOutput.html#writeInt(int)
|
|
*/
|
|
static inline size_t pack_int32(LMK_KILL_OCCURRED_PACKET packet,
|
|
size_t index,
|
|
int32_t value) {
|
|
int32_t* int_buffer = (int32_t*)(packet + index);
|
|
|
|
*int_buffer = htonl(value);
|
|
|
|
return index + sizeof(int32_t);
|
|
}
|
|
|
|
/**
|
|
* Writes int64 in a machine independent way
|
|
* https://docs.oracle.com/javase/7/docs/api/java/io/DataOutput.html#writeLong(long)
|
|
*/
|
|
static inline size_t pack_int64(LMK_KILL_OCCURRED_PACKET packet,
|
|
size_t index,
|
|
int64_t value) {
|
|
int64_t* int64_buffer = (int64_t*)(packet + index);
|
|
|
|
*int64_buffer = htonq(value);
|
|
|
|
return index + sizeof(int64_t);
|
|
}
|
|
|
|
/**
|
|
* Writes ANSI string in a machine independent way
|
|
* https://docs.oracle.com/javase/7/docs/api/java/io/DataOutput.html#writeShort(int)
|
|
* 2 bytes str len following n chars
|
|
* to be read on the Java side with
|
|
* https://docs.oracle.com/javase/7/docs/api/java/io/DataInput.html#readUTF()
|
|
* Truncates the value string & packs up to MAX_TASKNAME_LEN - 1 chars
|
|
*/
|
|
static inline size_t pack_string(LMK_KILL_OCCURRED_PACKET packet,
|
|
size_t index,
|
|
const char* value) {
|
|
const size_t len_proc_name = MIN(strlen(value), MAX_TASKNAME_LEN - 1);
|
|
int16_t* short_buffer = (int16_t*)(packet + index);
|
|
*short_buffer = htons((int16_t)len_proc_name);
|
|
|
|
char* byte_buffer = (char*)(short_buffer + 1);
|
|
strncpy(byte_buffer, value, MAX_TASKNAME_LEN - 1);
|
|
byte_buffer[MAX_TASKNAME_LEN - 1] = '\0';
|
|
|
|
return index + sizeof(int16_t) + len_proc_name + 1;
|
|
}
|
|
|
|
size_t lmkd_pack_set_kill_occurred(LMK_KILL_OCCURRED_PACKET packet,
|
|
struct kill_stat *kill_stat,
|
|
struct memory_stat *mem_stat) {
|
|
BUILD_BUG_ON(sizeof(LMK_KILL_OCCURRED_PACKET) > LMKD_REPLY_MAX_SIZE);
|
|
|
|
if (!enable_stats_log) {
|
|
return 0;
|
|
}
|
|
|
|
int32_t index = 0;
|
|
index = pack_int32(packet, index, LMK_STAT_KILL_OCCURRED);
|
|
|
|
if (mem_stat) {
|
|
index = pack_int64(packet, index, mem_stat->pgfault);
|
|
index = pack_int64(packet, index, mem_stat->pgmajfault);
|
|
index = pack_int64(packet, index, mem_stat->rss_in_bytes);
|
|
index = pack_int64(packet, index, mem_stat->cache_in_bytes);
|
|
index = pack_int64(packet, index, mem_stat->swap_in_bytes);
|
|
index = pack_int64(packet, index, mem_stat->process_start_time_ns);
|
|
} else {
|
|
index = pack_int64(packet, index, -1);
|
|
index = pack_int64(packet, index, -1);
|
|
index = pack_int64(packet, index, -1);
|
|
index = pack_int64(packet, index, -1);
|
|
index = pack_int64(packet, index, -1);
|
|
index = pack_int64(packet, index, -1);
|
|
}
|
|
|
|
index = pack_int32(packet, index, kill_stat->uid);
|
|
index = pack_int32(packet, index, kill_stat->oom_score);
|
|
index = pack_int32(packet, index, kill_stat->min_oom_score);
|
|
index = pack_int32(packet, index, (int)kill_stat->free_mem_kb);
|
|
index = pack_int32(packet, index, (int)kill_stat->free_swap_kb);
|
|
index = pack_int32(packet, index, (int)kill_stat->kill_reason);
|
|
index = pack_int32(packet, index, kill_stat->thrashing);
|
|
index = pack_int32(packet, index, kill_stat->max_thrashing);
|
|
|
|
index = pack_string(packet, index, kill_stat->taskname);
|
|
return index;
|
|
}
|
|
|
|
size_t lmkd_pack_set_state_changed(LMKD_CTRL_PACKET packet,
|
|
enum lmk_state state) {
|
|
if (!enable_stats_log) {
|
|
return 0;
|
|
}
|
|
|
|
packet[0] = htonl(LMK_STAT_STATE_CHANGED);
|
|
packet[1] = htonl(state);
|
|
|
|
return 2 * sizeof(int);
|
|
}
|
|
|
|
#endif /* LMKD_LOG_STATS */
|