diff options
Diffstat (limited to 'common.c')
-rw-r--r-- | common.c | 572 |
1 files changed, 572 insertions, 0 deletions
diff --git a/common.c b/common.c new file mode 100644 index 0000000..9602867 --- /dev/null +++ b/common.c @@ -0,0 +1,572 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include "common.h" + +// Globals +const char mstat_magic_bytes[] = MSTAT_MAGIC; +char *mstat_field_names[] = { + "pid", + "timestamp", + "rss", + "pss", + "pss_anon", + "pss_file", + "pss_shmem", + "shared_clean", + "shared_dirty", + "private_clean", + "private_dirty", + "referenced", + "anonymous", + "lazy_free", + "anon_huge_pages", + "shmem_pmd_mapped", + "file_pmd_mapped", + "shared_hugetlb", + "private_hugetlb", + "swap", + "swap_pss", + "locked", + NULL, +}; + +/** + * Get total number of fields stored in MSTAT file header + * @param fp pointer to MSTAT file stream + * @return + */ +int mstat_get_field_count(FILE *fp) { + int count; + ssize_t pos; + + count = 0; + pos = ftell(fp); + if (pos < 0) { + return -1; + } + if (fseek(fp, MSTAT_FIELD_COUNT, SEEK_SET) < 0) { + return -1; + } + if (!fread(&count, sizeof(count), 1, fp)) { + return -1; + } + if (fseek(fp, pos, SEEK_SET) < 0) { + return -1; + } + return count; +} + +/** + * Read fields stored in MSTAT header + * @param fp a pointer to MSTAT file + * @return array of MSTAT fields. NULL on error. + */ +char **mstat_read_fields(FILE *fp) { + char **fields; + int total = 0; + //fseek(fp, MSTAT_FIELD_COUNT, SEEK_SET); + //fread(&total, sizeof(total), 1, fp); + total = mstat_get_field_count(fp); + fseek(fp, MSTAT_MAGIC_SIZE, SEEK_SET); + fields = calloc(total + 1, sizeof(*fields)); + if (!fields) { + perror("Unable to allocate memory for fields"); + return NULL; + } + for (unsigned i = 0; i < total; i++) { + char buf[255] = {0}; + unsigned len; + fread(&len, sizeof(len), 1, fp); + fread(buf, len, 1, fp); + fields[i] = strdup(buf); + } + return fields; +} + +/** + * Check if `name` is present in `fields` array + * @param fields array of field names + * @param name field name to verify + * @return 0 on success. 1 on error. + */ +int mstat_is_valid_field(char **fields, const char *name) { + for (size_t i = 0; fields[i] != NULL; i++) { + if (!strcmp(fields[i], name)) { + return 0; + } + } + return 1; +} + +/** + * Return record value by field name + * @param p pointer to MSTAT record + * @param name field name + * @return MSTAT field union. ULLONG_MAX on error + */ +union mstat_field_t mstat_get_field_by_name(const struct mstat_record_t *p, const char *name) { + union mstat_field_t result; + result.u64 = ULLONG_MAX; + + if (!strcmp(name, "pid")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_PID); + } else if (!strcmp(name, "timestamp")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_TIMESTAMP); + } else if (!strcmp(name, "rss")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_RSS); + } else if (!strcmp(name, "pss")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_PSS); + } else if (!strcmp(name, "pss_anon")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_PSS_ANON); + } else if (!strcmp(name, "pss_file")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_PSS_FILE); + } else if (!strcmp(name, "pss_shmem")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_PSS_SHMEM); + } else if (!strcmp(name, "shared_clean")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_SHARED_CLEAN); + } else if (!strcmp(name, "shared_dirty")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_SHARED_DIRTY); + } else if (!strcmp(name, "private_clean")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_PRIVATE_CLEAN); + } else if (!strcmp(name, "private_dirty")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_PRIVATE_DIRTY); + } else if (!strcmp(name, "referenced")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_REFERENCED); + } else if (!strcmp(name, "anonymous")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_ANONYMOUS); + } else if (!strcmp(name, "lazy_free")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_LAZY_FREE); + } else if (!strcmp(name, "anon_huge_pages")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_ANON_HUGE_PAGES); + } else if (!strcmp(name, "shmem_pmd_mapped")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_SHMEM_PMD_MAPPED); + } else if (!strcmp(name, "file_pmd_mapped")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_FILE_PMD_MAPPED); + } else if (!strcmp(name, "shared_hugetlb")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_SHARED_HUGETLB); + } else if (!strcmp(name, "private_hugetlb")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_PRIVATE_HUGETLB); + } else if (!strcmp(name, "swap")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_SWAP); + } else if (!strcmp(name, "swap_pss")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_SWAP_PSS); + } else if (!strcmp(name, "locked")) { + result = mstat_get_field_by_id(p, MSTAT_FIELD_LOCKED); + } + + return result; +} + +/** + * Return record value by identifier + * @param record pointer to MSTAT record + * @param id MSTAT_FIELD_* constant + * @return MSTAT field union. ULLONG_MAX on error + */ +union mstat_field_t mstat_get_field_by_id(const struct mstat_record_t *record, unsigned id) { + union mstat_field_t result; + result.u64 = ULLONG_MAX; + + switch (id) { + case MSTAT_FIELD_PID: + result.u64 = record->pid; + break; + case MSTAT_FIELD_TIMESTAMP: + result.d64 = record->timestamp; + break; + case MSTAT_FIELD_RSS: + result.u64 = record->rss; + break; + case MSTAT_FIELD_PSS: + result.u64 = record->pss; + break; + case MSTAT_FIELD_PSS_ANON: + result.u64 = record->pss_anon; + break; + case MSTAT_FIELD_PSS_FILE: + result.u64 = record->pss_file; + break; + case MSTAT_FIELD_PSS_SHMEM: + result.u64 = record->pss_shmem; + break; + case MSTAT_FIELD_SHARED_CLEAN: + result.u64 = record->shared_clean; + break; + case MSTAT_FIELD_SHARED_DIRTY: + result.u64 = record->shared_dirty; + break; + case MSTAT_FIELD_PRIVATE_CLEAN: + result.u64 = record->private_clean; + break; + case MSTAT_FIELD_PRIVATE_DIRTY: + result.u64 = record->private_dirty; + break; + case MSTAT_FIELD_REFERENCED: + result.u64 = record->referenced; + break; + case MSTAT_FIELD_ANONYMOUS: + result.u64 = record->anonymous; + break; + case MSTAT_FIELD_LAZY_FREE: + result.u64 = record->lazy_free; + break; + case MSTAT_FIELD_ANON_HUGE_PAGES: + result.u64 = record->anon_huge_pages; + break; + case MSTAT_FIELD_SHMEM_PMD_MAPPED: + result.u64 = record->shmem_pmd_mapped; + break; + case MSTAT_FIELD_FILE_PMD_MAPPED: + result.u64 = record->file_pmd_mapped; + break; + case MSTAT_FIELD_SHARED_HUGETLB: + result.u64 = record->shared_hugetlb; + break; + case MSTAT_FIELD_PRIVATE_HUGETLB: + result.u64 = record->private_hugetlb; + break; + case MSTAT_FIELD_SWAP: + result.u64 = record->swap; + break; + case MSTAT_FIELD_SWAP_PSS: + result.u64 = record->swap_pss; + break; + case MSTAT_FIELD_LOCKED: + result.u64 = record->locked; + break; + default: + fprintf(stderr, "%s: unknown id id: %u\n", __FUNCTION__, id); + break; + } + + return result; +} + +int mstat_check_header(FILE *fp) { + int result; + char buf[MSTAT_MAGIC_SIZE] = {0}; + ssize_t pos = ftell(fp); + fseek(fp, 0, SEEK_SET); + fread(buf, sizeof(buf), 1, fp); + if (!strcmp(buf, mstat_magic_bytes)) { + result = 0; + } else { + result = 1; + } + fseek(fp, pos, SEEK_SET); + return result; +} + +/** + * Open an mstat file, or create one if it does not exist + * @param filename + * @return + */ +FILE *mstat_open(const char *filename) { + FILE *fp = NULL; + char mode[4] = {0}; + int do_header = 0; + + strcpy(mode, "rb+"); + if (access(filename, F_OK) < 0) { + do_header = 1; + strcpy(mode, "wb+"); + } + + fp = fopen(filename, mode); + if (!fp) { + perror(filename); + return NULL; + } + + if (do_header && mstat_write_header(fp)) { + fclose(fp); + fprintf(stderr, "unable to write header to mstat database\n"); + return NULL; + } else { + if (mstat_check_header(fp)) { + fprintf(stderr, "%s is not an mstat database\n", filename); + fclose(fp); + return NULL; + } + } + mstat_rewind(fp); + return fp; +} + +/** + * Rewind MSTAT file to the start of the data region + * @param fp pointer to MSTAT file stream + * @return + */ +int mstat_rewind(FILE *fp) { + int fields_end; + fseek(fp, MSTAT_EOH, SEEK_SET); + fread(&fields_end, sizeof(fields_end), 1, fp); + return fseek(fp, fields_end, SEEK_SET); +} + +/** + * Return one record from a MSTAT file per call, until EOF + * @param fp pointer to MSTAT file stream + * @param record pointer to MSTAT record + * @return 0 on success. -1 on error + */ +int mstat_iter(FILE *fp, struct mstat_record_t *record) { + if (feof(fp)) + return -1; + if (!fread(&record->pid, sizeof(record->pid), 1, fp)) return -1; + if (!fread(&record->timestamp, sizeof(record->timestamp), 1, fp)) return -1; + if (!fread(&record->rss, sizeof(record->rss), 1, fp)) return -1; + if (!fread(&record->pss, sizeof(record->pss), 1, fp)) return -1; + if (!fread(&record->pss_anon, sizeof(record->pss_anon), 1, fp)) return -1; + if (!fread(&record->pss_file, sizeof(record->pss_file), 1, fp)) return -1; + if (!fread(&record->pss_shmem, sizeof(record->pss_shmem), 1, fp)) return -1; + if (!fread(&record->shared_clean, sizeof(record->shared_clean), 1, fp)) return -1; + if (!fread(&record->shared_dirty, sizeof(record->shared_dirty), 1, fp)) return -1; + if (!fread(&record->private_clean, sizeof(record->private_clean), 1, fp)) return -1; + if (!fread(&record->private_dirty, sizeof(record->private_dirty), 1, fp)) return -1; + if (!fread(&record->referenced, sizeof(record->referenced), 1, fp)) return -1; + if (!fread(&record->anonymous, sizeof(record->anonymous), 1, fp)) return -1; + if (!fread(&record->lazy_free, sizeof(record->lazy_free), 1, fp)) return -1; + if (!fread(&record->anon_huge_pages, sizeof(record->anon_huge_pages), 1, fp)) return -1; + if (!fread(&record->shmem_pmd_mapped, sizeof(record->shmem_pmd_mapped), 1, fp)) return -1; + if (!fread(&record->file_pmd_mapped, sizeof(record->file_pmd_mapped), 1, fp)) return -1; + if (!fread(&record->shared_hugetlb, sizeof(record->shared_hugetlb), 1, fp)) return -1; + if (!fread(&record->private_hugetlb, sizeof(record->private_hugetlb), 1, fp)) return -1; + if (!fread(&record->swap, sizeof(record->swap), 1, fp)) return -1; + if (!fread(&record->swap_pss, sizeof(record->swap_pss), 1, fp)) return -1; + if (!fread(&record->locked, sizeof(record->locked), 1, fp)) return -1; + return 0; +} + +/** + * Convert smaps_rollup data string to integer + * @param data value from smaps_rollup key pair + * @return integer value on success. -1 on error + */ +ssize_t mstat_get_value_smaps(char *data) { + ssize_t result = -1; + char *ptr = NULL; + + ptr = strchr(data, ':'); + if (ptr) { + ptr++; + result = strtol(ptr, NULL, 10); + } + return result; +} + +/** + * Extract value (as string) from smaps_rollup key pair + * @param data smaps_rollup line + * @param key name to read + * @return data from key on success. NULL on error + */ +char *mstat_get_key_smaps(char *data, const char *key) { + char buf[255] = {0}; + snprintf(buf, sizeof(buf) - 1, "%s:", key); + if (!strncmp(data, buf, strlen(buf))) { + return data; + } + return NULL; +} + +/** + * Consume /proc/`pid`/smaps_rollup stream + * @param p pointer to MSTAT record + * @param fp pointer to file stream + * @return TODO + */ +int mstat_read_smaps(struct mstat_record_t *p, FILE *fp) { + char data[1024] = {0}; + for (size_t i = 0; fgets(data, sizeof(data) - 1, fp) != NULL; i++) { + if (mstat_get_key_smaps(data, "Rss")) { + p->rss = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "Pss")) { + p->pss = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "Pss_Anon")) { + p->pss_anon = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "Pss_File")) { + p->pss_file = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "Pss_Shmem")) { + p->pss_shmem = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "Shared_Clean")) { + p->shared_clean = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "Shared_Dirty")) { + p->shared_dirty = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "Private_Clean")) { + p->private_clean = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "Private_Dirty")) { + p->private_dirty = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "Referenced")) { + p->referenced = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "Anonymous")) { + p->anonymous = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "LazyFree")) { + p->lazy_free = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "AnonHugePages")) { + p->anon_huge_pages = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "ShmemPmdMapped")) { + p->shmem_pmd_mapped = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "FilePmdMapped")) { + p->file_pmd_mapped = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "Shared_Hugetlb")) { + p->shared_hugetlb = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "Private_Hugetlb")) { + p->private_hugetlb = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "Swap")) { + p->swap = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "SwapPss")) { + p->swap_pss = mstat_get_value_smaps(data); + } + if (mstat_get_key_smaps(data, "Locked")) { + p->locked = mstat_get_value_smaps(data); + } + } +} + +/** + * + * @param p pointer to MSTAT record + * @param pid of target process + * @return 0 on success, -1 on error + */ +int mstat_attach(struct mstat_record_t *p, pid_t pid) { + FILE *fp; + char path[PATH_MAX] = {0}; + + snprintf(path, PATH_MAX, "/proc/%d/smaps_rollup", pid); + if (access(path, F_OK) < 0) { + return -1; + } + fp = fopen(path, "r"); + if (!fp) { + return -1; + } + + mstat_read_smaps(p, fp); + fclose(fp); + + return 0; +} + +/** + * Write MSTAT header to data file + * + * HEADER FORMAT + * 0x00 - 0x07 = file identifier (8 bytes) + * 0x08 - 0x0B = total field records (4 bytes) + * 0x0C - 0x0F = EOH offset (4 bytes) + * 0x10 - EOH = field_length (unsigned int), field (string) (n... bytes) + * + * @param fp pointer to stream + * @return 0 on success, -1 on error + */ +int mstat_write_header(FILE *fp) { + fwrite(mstat_magic_bytes, 6, 1, fp); + for (int i = 0; i < MSTAT_MAGIC_SIZE - sizeof(mstat_magic_bytes); i++) { + if (!fwrite("\0", 1, 1, fp)) { + return -1; + } + } + int rec; + ssize_t fields_end; + + for (rec = 0; mstat_field_names[rec] != NULL; rec++) { + unsigned int len = strlen(mstat_field_names[rec]); + fwrite(&len, sizeof(len), 1, fp); + fwrite(mstat_field_names[rec], sizeof(char), len, fp); + } + fields_end = ftell(fp); + + fseek(fp, MSTAT_FIELD_COUNT, SEEK_SET); + fwrite(&rec, sizeof(rec), 1, fp); + + fseek(fp, MSTAT_EOH, SEEK_SET); + fwrite(&fields_end, sizeof(int), 1, fp); + fseek(fp, fields_end, SEEK_SET); + return 0; +} + +/** + * Write a MSTAT record to data file + * @param fp pointer to MSTAT file stream + * @param record pointer to MSTAT record + * @return 0 on success. -1 on error + */ +int mstat_write(FILE *fp, struct mstat_record_t *record) { + if (!fwrite(&record->pid, sizeof(record->pid), 1, fp)) return -1; + if (!fwrite(&record->timestamp, sizeof(record->timestamp), 1, fp)) return -1; + if (!fwrite(&record->rss, sizeof(record->rss), 1, fp)) return -1; + if (!fwrite(&record->pss, sizeof(record->pss), 1, fp)) return -1; + if (!fwrite(&record->pss_anon, sizeof(record->pss_anon), 1, fp)) return -1; + if (!fwrite(&record->pss_file, sizeof(record->pss_file), 1, fp)) return -1; + if (!fwrite(&record->pss_shmem, sizeof(record->pss_shmem), 1, fp)) return -1; + if (!fwrite(&record->shared_clean, sizeof(record->shared_clean), 1, fp)) return -1; + if (!fwrite(&record->shared_dirty, sizeof(record->shared_dirty), 1, fp)) return -1; + if (!fwrite(&record->private_clean, sizeof(record->private_clean), 1, fp)) return -1; + if (!fwrite(&record->private_dirty, sizeof(record->private_dirty), 1, fp)) return -1; + if (!fwrite(&record->referenced, sizeof(record->referenced), 1, fp)) return -1; + if (!fwrite(&record->anonymous, sizeof(record->anonymous), 1, fp)) return -1; + if (!fwrite(&record->lazy_free, sizeof(record->lazy_free), 1, fp)) return -1; + if (!fwrite(&record->anon_huge_pages, sizeof(record->anon_huge_pages), 1, fp)) return -1; + if (!fwrite(&record->shmem_pmd_mapped, sizeof(record->shmem_pmd_mapped), 1, fp)) return -1; + if (!fwrite(&record->file_pmd_mapped, sizeof(record->file_pmd_mapped), 1, fp)) return -1; + if (!fwrite(&record->shared_hugetlb, sizeof(record->shared_hugetlb), 1, fp)) return -1; + if (!fwrite(&record->private_hugetlb, sizeof(record->private_hugetlb), 1, fp)) return -1; + if (!fwrite(&record->swap, sizeof(record->swap), 1, fp)) return -1; + if (!fwrite(&record->swap_pss, sizeof(record->swap_pss), 1, fp)) return -1; + if (!fwrite(&record->locked, sizeof(record->locked), 1, fp)) return -1; + return 0; +} + +/** + * Compute difference between timespec structures + * @param end timespec + * @param start timespec + * @return seconds + */ +double mstat_difftimespec(const struct timespec end, const struct timespec start) { + return (double)(end.tv_sec - start.tv_sec) + (double)(end.tv_nsec - start.tv_nsec) / 1e9; +} + +/** + * Compute the min/max of an array + * @param a input data + * @param size size of input data + * @param min pointer to return variable (modified) + * @param max pointer to return variable (modified) + */ +void mstat_get_mmax(const double a[], size_t size, double *min, double *max) { + *min = a[0]; + *max = 0; + + for (size_t i = 0; i < size; i++) { + if (a[i] > *max) { + *max = a[i]; + } + if (a[i] < *min) { + *min = a[i]; + } + } +}
\ No newline at end of file |