Compare commits
10 Commits
810586053d
...
692f9dbd32
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
692f9dbd32 | ||
|
|
afc17db869 | ||
|
|
54f22771dc | ||
|
|
82fb0ec4bd | ||
|
|
46e5fca147 | ||
|
|
3960919efe | ||
|
|
4e9da25009 | ||
|
|
187bf253e3 | ||
|
|
89b05452a0 | ||
|
|
5902bc0bb5 |
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"stdlib.h": "c",
|
"stdlib.h": "c",
|
||||||
"process.h": "c"
|
"process.h": "c",
|
||||||
|
"unistd.h": "c"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
#include "lxcstat.h"
|
#include "lxcstat.h"
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@ -42,6 +43,32 @@ int readLxcConfig(lxcinfo *lxc, int lxcid) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void getInactiveLXCs(lxcinfo **lxcs) {
|
||||||
|
struct dirent *pDirent;
|
||||||
|
DIR *pDir;
|
||||||
|
|
||||||
|
// Ensure we can open directory.
|
||||||
|
pDir = opendir("/etc/pve/lxc");
|
||||||
|
if (pDir == NULL) {
|
||||||
|
printf("Cannot open directory '/etc/pve/lxc'\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each entry.
|
||||||
|
while ((pDirent = readdir(pDir)) != NULL) {
|
||||||
|
char *fname = pDirent->d_name;
|
||||||
|
char extension[20];
|
||||||
|
int lxcid;
|
||||||
|
|
||||||
|
int argc = sscanf(fname, "%d.%s", &lxcid, (char *) extension);
|
||||||
|
// ensure it's an LXC config
|
||||||
|
if (argc != 2 || strcmp((char *) extension, "conf") != 0) continue;
|
||||||
|
getLXCInfo(lxcs, lxcid); // don't care about result
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(pDir);
|
||||||
|
}
|
||||||
|
|
||||||
lxcinfo *getLXCInfo(lxcinfo **lxcs, int lxcid) {
|
lxcinfo *getLXCInfo(lxcinfo **lxcs, int lxcid) {
|
||||||
lxcinfo *lxc = *lxcs;
|
lxcinfo *lxc = *lxcs;
|
||||||
while (lxc != NULL) {
|
while (lxc != NULL) {
|
||||||
@ -51,6 +78,7 @@ lxcinfo *getLXCInfo(lxcinfo **lxcs, int lxcid) {
|
|||||||
|
|
||||||
lxc = calloc(1, sizeof(lxcinfo));
|
lxc = calloc(1, sizeof(lxcinfo));
|
||||||
lxc->lxcid = lxcid;
|
lxc->lxcid = lxcid;
|
||||||
|
lxc->running = 0;
|
||||||
|
|
||||||
if (readLxcConfig(lxc, lxcid)) {
|
if (readLxcConfig(lxc, lxcid)) {
|
||||||
// failed to read LXC
|
// failed to read LXC
|
||||||
|
|||||||
@ -5,6 +5,7 @@ typedef struct _lxc_info {
|
|||||||
struct _lxc_info *next;
|
struct _lxc_info *next;
|
||||||
struct _lxc_info *head;
|
struct _lxc_info *head;
|
||||||
struct _lxc_info *tail; // only valid for head lxcinfo
|
struct _lxc_info *tail; // only valid for head lxcinfo
|
||||||
|
int running;
|
||||||
int lxcid;
|
int lxcid;
|
||||||
int cpucount;
|
int cpucount;
|
||||||
int memlimit;
|
int memlimit;
|
||||||
@ -13,5 +14,6 @@ typedef struct _lxc_info {
|
|||||||
} lxcinfo;
|
} lxcinfo;
|
||||||
|
|
||||||
lxcinfo *getLXCInfo(lxcinfo **lxcs, int lxcid);
|
lxcinfo *getLXCInfo(lxcinfo **lxcs, int lxcid);
|
||||||
|
void getInactiveLXCs(lxcinfo **lxcs);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@ -34,7 +34,7 @@ static inline int64_t fast_str2ll(char **str) {
|
|||||||
|
|
||||||
static inline uint64_t findAndParseField(char **filedata, char *fld) {
|
static inline uint64_t findAndParseField(char **filedata, char *fld) {
|
||||||
char *field = strstr(*filedata, fld);
|
char *field = strstr(*filedata, fld);
|
||||||
if (!field) return -1;
|
if (!field) return 0;
|
||||||
while (*field < '0' || *field > '9') field++;
|
while (*field < '0' || *field > '9') field++;
|
||||||
*filedata = field;
|
*filedata = field;
|
||||||
uint64_t val = fast_str2ull(filedata);
|
uint64_t val = fast_str2ull(filedata);
|
||||||
|
|||||||
396
src/process.c
Normal file
396
src/process.c
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
#include <dirent.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "process.h"
|
||||||
|
#include "lxcstat.h"
|
||||||
|
#include "parsing.h"
|
||||||
|
|
||||||
|
#ifndef PF_KTHREAD
|
||||||
|
#define PF_KTHREAD 0x00200000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define skipRange(inclusive, exclusive) \
|
||||||
|
for (int i##inclusive = 0; i##inclusive < exclusive - inclusive; \
|
||||||
|
i##inclusive++) \
|
||||||
|
location = strchr(location, ' ') + 1;
|
||||||
|
|
||||||
|
int parseStatFile(Process *proc, char *filedata) {
|
||||||
|
char *location = filedata;
|
||||||
|
// (1) pid - %d
|
||||||
|
proc->pid = fast_str2ull(&location);
|
||||||
|
location += 2;
|
||||||
|
|
||||||
|
// (2) comm - %s
|
||||||
|
char *end = strrchr(location, ')');
|
||||||
|
size_t length = end - location;
|
||||||
|
memcpy(proc->label, location, length);
|
||||||
|
proc->label[length] = 0;
|
||||||
|
location = end + 2;
|
||||||
|
|
||||||
|
// (3) State - %c
|
||||||
|
location += 2;
|
||||||
|
|
||||||
|
// (4) ppid - %d
|
||||||
|
proc->ppid = fast_str2ull(&location);
|
||||||
|
location += 1;
|
||||||
|
|
||||||
|
// skip [5 - 9)
|
||||||
|
skipRange(5, 9);
|
||||||
|
|
||||||
|
// (9) flags - %u
|
||||||
|
proc->flags = fast_str2ull(&location);
|
||||||
|
location += 1;
|
||||||
|
|
||||||
|
proc->iskernel = (proc->flags & PF_KTHREAD) ? true : false;
|
||||||
|
|
||||||
|
// skip [10 - 14)
|
||||||
|
skipRange(10, 14);
|
||||||
|
|
||||||
|
// (14) utime - %lu
|
||||||
|
proc->cpuTime = fast_str2ull(&location);
|
||||||
|
location += 1;
|
||||||
|
|
||||||
|
// (15) stime - %lu
|
||||||
|
proc->cpuTime += fast_str2ull(&location);
|
||||||
|
location += 1;
|
||||||
|
|
||||||
|
// (16) cutime - %lu
|
||||||
|
proc->cpuTime += fast_str2ull(&location);
|
||||||
|
location += 1;
|
||||||
|
|
||||||
|
// (17) cstime - %lu
|
||||||
|
proc->cpuTime += fast_str2ull(&location);
|
||||||
|
proc->cCpuTime = proc->cpuTime;
|
||||||
|
location += 1;
|
||||||
|
|
||||||
|
// skip 18-19
|
||||||
|
skipRange(18, 20);
|
||||||
|
|
||||||
|
// (20) num_threads - %ld
|
||||||
|
proc->nThreads = fast_str2ull(&location);
|
||||||
|
location += 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parseStatusFile(Process *proc, char *filedata) {
|
||||||
|
proc->tgid = findAndParseField(&filedata, "Tgid");
|
||||||
|
proc->pid = findAndParseField(&filedata, "Pid");
|
||||||
|
|
||||||
|
if (proc->pid != proc->tgid)
|
||||||
|
return -1; // ignore child threads, as their stats are the same as the
|
||||||
|
// parent
|
||||||
|
|
||||||
|
proc->resident = 1024 * findAndParseField(&filedata, "VmRSS");
|
||||||
|
proc->swap = 1024 * findAndParseField(&filedata, "VmSwap");
|
||||||
|
proc->cMemory = proc->resident;
|
||||||
|
proc->cSwap = proc->swap;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parseIOFile(Process *proc, char *filedata) {
|
||||||
|
proc->read = findAndParseField(&filedata, "read_bytes");
|
||||||
|
proc->written = findAndParseField(&filedata, "write_bytes");
|
||||||
|
proc->cRead = proc->read;
|
||||||
|
proc->cWrite = proc->written;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int resetVisits(Process *head) {
|
||||||
|
Process *current = head;
|
||||||
|
int processCount = 0;
|
||||||
|
|
||||||
|
while (current != NULL) {
|
||||||
|
current->visited = 0;
|
||||||
|
current = current->next;
|
||||||
|
processCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return processCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void linkFamily(Process *head) {
|
||||||
|
int orphans = 1;
|
||||||
|
|
||||||
|
while (orphans) {
|
||||||
|
orphans = 0;
|
||||||
|
Process *current = head;
|
||||||
|
// while on an actual process node, and it has a parent link or doesn't need a parent link (kernel)
|
||||||
|
while (current != NULL && (current->parent != NULL || current->ppid == current->pid)) current = current->next;
|
||||||
|
if (current == NULL) break;
|
||||||
|
orphans = 1;
|
||||||
|
|
||||||
|
Process *parent = head;
|
||||||
|
while (parent != NULL && parent->pid != current->ppid) parent = parent->next;
|
||||||
|
if (parent == NULL) {
|
||||||
|
printf("Found orphan process: %d\n", current->pid);
|
||||||
|
current->ppid = -1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a parent and a child ready to be united
|
||||||
|
current->parent = parent;
|
||||||
|
Process **placement = &(parent->child);
|
||||||
|
while ((*placement) != NULL) placement = &((*placement)->sibling);
|
||||||
|
*placement = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printFamilyTree(Process *head) {
|
||||||
|
int processCount = resetVisits(head);
|
||||||
|
int visited = 0;
|
||||||
|
Process *current = head;
|
||||||
|
int depth = 0;
|
||||||
|
|
||||||
|
while (visited < processCount) {
|
||||||
|
if (current == NULL) {
|
||||||
|
printf("Bad visit count: %d visited of %d processes\n", visited, processCount);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current->visited = 1;
|
||||||
|
visited++;
|
||||||
|
|
||||||
|
printf("%10d %10d %1d ", current->pid, current->ppid, current->iskernel);
|
||||||
|
// `ps f` style tree
|
||||||
|
for (int i=1; i<depth; i++) printf(" ");
|
||||||
|
if (depth > 0) printf(" \\_ ");
|
||||||
|
printf("%s\n", current->label);
|
||||||
|
|
||||||
|
nextProcess:
|
||||||
|
if (current->child != NULL && !current->child->visited) {
|
||||||
|
// process children first
|
||||||
|
depth++;
|
||||||
|
current = current->child;
|
||||||
|
} else if (current->sibling != NULL) {
|
||||||
|
// process siblings next
|
||||||
|
current = current->sibling;
|
||||||
|
} else if (current->parent != NULL) {
|
||||||
|
// return to parent when tree is exhausted
|
||||||
|
depth--;
|
||||||
|
current = current->parent;
|
||||||
|
goto nextProcess; // parent was already visited, so find next process from parent
|
||||||
|
} else {
|
||||||
|
// no parent - scan for unvisited process
|
||||||
|
while (current != NULL && current->visited) current = current->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void aggregateStats(Process *head) {
|
||||||
|
int processCount = resetVisits(head);
|
||||||
|
Process *current = head;
|
||||||
|
int depth = 0;
|
||||||
|
int maxdepth = 0;
|
||||||
|
int visited = 0;
|
||||||
|
|
||||||
|
// assign depth (aka level) of each process
|
||||||
|
while (visited < processCount) {
|
||||||
|
if (current == NULL) {
|
||||||
|
printf("Bad visit count: %d visited of %d processes\n", visited, processCount);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
visited++;
|
||||||
|
current->visited = visited; // store to tag the order the process is visited
|
||||||
|
current->level = depth;
|
||||||
|
|
||||||
|
if (depth > maxdepth) maxdepth = depth;
|
||||||
|
|
||||||
|
nextProcess:
|
||||||
|
if (current->child != NULL && !current->child->visited) {
|
||||||
|
// process children first
|
||||||
|
depth++;
|
||||||
|
current = current->child;
|
||||||
|
} else if (current->sibling != NULL) {
|
||||||
|
// process siblings next
|
||||||
|
current = current->sibling;
|
||||||
|
} else if (current->parent != NULL) {
|
||||||
|
// return to parent when tree is exhausted
|
||||||
|
depth--;
|
||||||
|
current = current->parent;
|
||||||
|
goto nextProcess; // parent was already visited, so find next process from parent
|
||||||
|
} else {
|
||||||
|
// no parent - scan for unvisited process
|
||||||
|
while (current != NULL && current->visited) current = current->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan for each depth level, tally stats upwards
|
||||||
|
for (depth=maxdepth; depth>0; depth--) {
|
||||||
|
current = head;
|
||||||
|
while (current != NULL) {
|
||||||
|
while (current != NULL && current->level != depth) current = current->next;
|
||||||
|
if (current == NULL) continue;
|
||||||
|
|
||||||
|
Process *parent = current->parent;
|
||||||
|
parent->cCpuTime += current->cCpuTime;
|
||||||
|
parent->cMemory += current->cMemory;
|
||||||
|
parent->cSwap += current->cSwap;
|
||||||
|
parent->cRead += current->cRead;
|
||||||
|
parent->cWrite += current->cWrite;
|
||||||
|
current = current->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char *readProcesses(char *procdir) {
|
||||||
|
int len;
|
||||||
|
struct dirent *pDirent;
|
||||||
|
DIR *pDir;
|
||||||
|
|
||||||
|
// Ensure we can open directory.
|
||||||
|
|
||||||
|
pDir = opendir(procdir);
|
||||||
|
if (pDir == NULL) {
|
||||||
|
printf("Cannot open directory '%s'\n", procdir);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each entry.
|
||||||
|
|
||||||
|
char *buffer = malloc(4096);
|
||||||
|
char *fname = malloc(1024);
|
||||||
|
char first;
|
||||||
|
FILE *file;
|
||||||
|
|
||||||
|
Process *head = calloc(1, sizeof(Process));
|
||||||
|
Process *cur = head;
|
||||||
|
Process *prev = NULL;
|
||||||
|
lxcinfo *lxcs = NULL;
|
||||||
|
|
||||||
|
// First process node is the kernel
|
||||||
|
strcpy(cur->label, "kernel");
|
||||||
|
cur->lxc = -1;
|
||||||
|
cur->visited = 1;
|
||||||
|
|
||||||
|
while ((pDirent = readdir(pDir)) != NULL) {
|
||||||
|
first = pDirent->d_name[0];
|
||||||
|
if (first < '0' || first > '9') continue;
|
||||||
|
|
||||||
|
if (cur->visited) {
|
||||||
|
// get new process if necessary
|
||||||
|
prev = cur;
|
||||||
|
cur->next = calloc(1, sizeof(Process));
|
||||||
|
cur = cur->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf(fname, "/proc/%s/status", pDirent->d_name);
|
||||||
|
file = fopen(fname, "rb");
|
||||||
|
if (file == NULL) continue;
|
||||||
|
fread(buffer, 1, 4096, file);
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
if (parseStatusFile(cur, buffer)) continue;
|
||||||
|
|
||||||
|
// truncate 'status' to 'stat'
|
||||||
|
len = strlen(fname);
|
||||||
|
fname[len - 2] = 0;
|
||||||
|
file = fopen(fname, "rb");
|
||||||
|
if (file == NULL) continue;
|
||||||
|
fread(buffer, 1, 4096, file);
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
if (parseStatFile(cur, buffer)) continue;
|
||||||
|
|
||||||
|
sprintf(fname, "/proc/%s/io", pDirent->d_name);
|
||||||
|
file = fopen(fname, "rb");
|
||||||
|
if (file == NULL) continue;
|
||||||
|
fread(buffer, 1, 4096, file);
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
if (parseIOFile(cur, buffer)) continue;
|
||||||
|
|
||||||
|
sprintf(fname, "/proc/%s/cpuset", pDirent->d_name);
|
||||||
|
file = fopen(fname, "rb");
|
||||||
|
if (file == NULL) goto nextProcess;
|
||||||
|
fread(cur->cpuset, 1, sizeof(cur->cpuset), file);
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
strtok(cur->cpuset, "\n\t ");
|
||||||
|
char *lxcTag = "/lxc/";
|
||||||
|
if (memcmp(cur->cpuset, lxcTag, strlen(lxcTag)) == 0) {
|
||||||
|
// Resides in LXC -- read file and tag
|
||||||
|
sscanf(cur->cpuset, "/lxc/%d/ns", &cur->lxc);
|
||||||
|
lxcinfo *lxc = getLXCInfo(&lxcs, cur->lxc);
|
||||||
|
if (lxc == NULL) {
|
||||||
|
printf("Failed to read LXC config <%d>\n", cur->lxc);
|
||||||
|
} else {
|
||||||
|
lxc->running = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cur->lxc = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextProcess:
|
||||||
|
cur->visited = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cur->visited) {
|
||||||
|
// if there's an unused node, remove it
|
||||||
|
prev->next = NULL;
|
||||||
|
free(cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
linkFamily(head);
|
||||||
|
aggregateStats(head);
|
||||||
|
getInactiveLXCs(&lxcs);
|
||||||
|
|
||||||
|
int clocks = sysconf(_SC_CLK_TCK);
|
||||||
|
char *output = malloc(8 * 1024 * 1024);
|
||||||
|
char *ptr = output;
|
||||||
|
|
||||||
|
cur = head;
|
||||||
|
while (cur != NULL) {
|
||||||
|
// create process descriptor
|
||||||
|
sprintf(buffer,
|
||||||
|
"pid=\"%d\",ppid=\"%d\",label=\"%s\",lxc=\"%d\"",
|
||||||
|
cur->pid, cur->ppid, cur->label, cur->lxc);
|
||||||
|
// process specific stats
|
||||||
|
ptr += sprintf(ptr, "process_cpu_time_seconds{%s} %f\n", buffer, (double) cur->cpuTime / clocks);
|
||||||
|
ptr += sprintf(ptr, "process_num_threads{%s} %lu\n", buffer, cur->nThreads);
|
||||||
|
ptr += sprintf(ptr, "process_resident_bytes{%s} %lu\n", buffer, cur->resident);
|
||||||
|
ptr += sprintf(ptr, "process_swap_bytes{%s} %lu\n", buffer, cur->swap);
|
||||||
|
ptr += sprintf(ptr, "process_fileio_bytes_written{%s} %lu\n", buffer, cur->written);
|
||||||
|
ptr += sprintf(ptr, "process_fileio_bytes_read{%s} %lu\n", buffer, cur->read);
|
||||||
|
ptr += sprintf(ptr, "process_is_kernel_process{%s} %d\n", buffer, cur->iskernel);
|
||||||
|
// cumulative fields
|
||||||
|
ptr += sprintf(ptr, "process_cumulative_cpu_time_seconds{%s} %lu\n", buffer, cur->cCpuTime);
|
||||||
|
ptr += sprintf(ptr, "process_cumulative_resident_bytes{%s} %lu\n", buffer, cur->cMemory);
|
||||||
|
ptr += sprintf(ptr, "process_cumulative_swap_bytes{%s} %lu\n", buffer, cur->cSwap);
|
||||||
|
ptr += sprintf(ptr, "process_cumulative_bytes_read{%s} %lu\n", buffer, cur->cRead);
|
||||||
|
ptr += sprintf(ptr, "process_cumulative_bytes_written{%s} %lu\n", buffer, cur->cWrite);
|
||||||
|
|
||||||
|
// stats used to make flame chart
|
||||||
|
ptr += sprintf(ptr, "process_tree_depth{%s} %d\n", buffer, cur->level);
|
||||||
|
ptr += sprintf(ptr, "process_visit_index{%s} %d\n", buffer, cur->visited);
|
||||||
|
// free and proceed
|
||||||
|
Process *prev = cur;
|
||||||
|
cur = cur->next;
|
||||||
|
free(prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
lxcinfo *lxc = lxcs;
|
||||||
|
while (lxc != NULL) {
|
||||||
|
sprintf(buffer, "lxc=\"%d\",lxcname=\"%s\"", lxc->lxcid, lxc->hostname);
|
||||||
|
ptr += sprintf(ptr, "lxc_cpu_core_count{%s} %u\n", buffer, lxc->cpucount);
|
||||||
|
ptr += sprintf(ptr, "lxc_memory_limit_bytes{%s} %llu\n", buffer, lxc->memlimit);
|
||||||
|
ptr += sprintf(ptr, "lxc_swap_limit_bytes{%s} %llu\n", buffer, lxc->swaplimit);
|
||||||
|
ptr += sprintf(ptr, "lxc_is_running{%s} %d\n", buffer, lxc->running);
|
||||||
|
// free and proceed
|
||||||
|
lxcinfo *prev = lxc;
|
||||||
|
lxc = lxc->next;
|
||||||
|
free(prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(pDir);
|
||||||
|
free(buffer);
|
||||||
|
free(fname);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
@ -6,7 +6,6 @@
|
|||||||
typedef struct st_process {
|
typedef struct st_process {
|
||||||
// For our own purposes
|
// For our own purposes
|
||||||
struct st_process *next; // next process in order we read them
|
struct st_process *next; // next process in order we read them
|
||||||
struct st_process *prev; // previous process read
|
|
||||||
struct st_process *parent; // parent process
|
struct st_process *parent; // parent process
|
||||||
struct st_process *child; // pointer to first child
|
struct st_process *child; // pointer to first child
|
||||||
struct st_process *sibling; // pointer to next sibling
|
struct st_process *sibling; // pointer to next sibling
|
||||||
@ -28,12 +27,24 @@ typedef struct st_process {
|
|||||||
uint64_t swap;
|
uint64_t swap;
|
||||||
uint64_t written;
|
uint64_t written;
|
||||||
uint64_t read;
|
uint64_t read;
|
||||||
|
// cumulative fields - process + children
|
||||||
uint64_t childCpuTime;
|
uint64_t cCpuTime;
|
||||||
uint64_t childMemory;
|
uint64_t cMemory;
|
||||||
uint64_t childSwap;
|
uint64_t cSwap;
|
||||||
uint64_t childRead;
|
uint64_t cRead;
|
||||||
uint64_t childWrite;
|
uint64_t cWrite;
|
||||||
} Process;
|
} Process;
|
||||||
|
|
||||||
|
char *readProcesses(char *procdir);
|
||||||
|
void aggregateStats(Process *head);
|
||||||
|
|
||||||
|
int resetVisits(Process *head);
|
||||||
|
void linkFamily(Process *head);
|
||||||
|
void printFamilyTree(Process *head);
|
||||||
|
int parseStatFile(Process *proc, char *filedata);
|
||||||
|
int parseStatusFile(Process *proc, char *filedata);
|
||||||
|
int parseIOFile(Process *proc, char *filedata);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
344
src/server.c
344
src/server.c
@ -1,7 +1,6 @@
|
|||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <dirent.h>
|
#include <pthread.h>
|
||||||
#include <stdbool.h>
|
#include <signal.h>
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@ -10,292 +9,37 @@
|
|||||||
|
|
||||||
#include "process.h"
|
#include "process.h"
|
||||||
#include "lxcstat.h"
|
#include "lxcstat.h"
|
||||||
#include "parsing.h"
|
|
||||||
|
|
||||||
#ifndef PF_KTHREAD
|
volatile sig_atomic_t stop_server = 0;
|
||||||
#define PF_KTHREAD 0x00200000
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define skipRange(inclusive, exclusive) \
|
void handle_signal(int signal) {
|
||||||
for (int i##inclusive = 0; i##inclusive < exclusive - inclusive; \
|
stop_server = 1; // Set the flag to stop the server
|
||||||
i##inclusive++) \
|
|
||||||
location = strchr(location, ' ') + 1;
|
|
||||||
|
|
||||||
int parseStatFile(Process *proc, char *filedata) {
|
|
||||||
char *location = filedata;
|
|
||||||
// (1) pid - %d
|
|
||||||
proc->pid = fast_str2ull(&location);
|
|
||||||
location += 2;
|
|
||||||
|
|
||||||
// (2) comm - %s
|
|
||||||
char *end = strrchr(location, ')');
|
|
||||||
size_t length = end - location;
|
|
||||||
memcpy(proc->label, location, length);
|
|
||||||
proc->label[length] = 0;
|
|
||||||
location = end + 2;
|
|
||||||
|
|
||||||
// (3) State - %c
|
|
||||||
location += 2;
|
|
||||||
|
|
||||||
// (4) ppid - %d
|
|
||||||
proc->ppid = fast_str2ull(&location);
|
|
||||||
location += 1;
|
|
||||||
|
|
||||||
// skip [5 - 9)
|
|
||||||
skipRange(5, 9);
|
|
||||||
|
|
||||||
// (9) flags - %u
|
|
||||||
proc->flags = fast_str2ull(&location);
|
|
||||||
location += 1;
|
|
||||||
|
|
||||||
proc->iskernel = (proc->flags & PF_KTHREAD) ? true : false;
|
|
||||||
|
|
||||||
// skip [10 - 14)
|
|
||||||
skipRange(10, 14);
|
|
||||||
|
|
||||||
// (14) utime - %lu
|
|
||||||
proc->cpuTime = fast_str2ull(&location);
|
|
||||||
location += 1;
|
|
||||||
|
|
||||||
// (15) stime - %lu
|
|
||||||
proc->cpuTime += fast_str2ull(&location);
|
|
||||||
location += 1;
|
|
||||||
|
|
||||||
// skip 16-19
|
|
||||||
skipRange(16, 20);
|
|
||||||
|
|
||||||
// (20) num_threads - %ld
|
|
||||||
proc->nThreads = fast_str2ull(&location);
|
|
||||||
location += 1;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int parseStatusFile(Process *proc, char *filedata) {
|
void *self_connector_thread(void *arg) {
|
||||||
proc->tgid = findAndParseField(&filedata, "Tgid");
|
int port = *((int *) arg);
|
||||||
proc->pid = findAndParseField(&filedata, "Pid");
|
|
||||||
|
|
||||||
if (proc->pid != proc->tgid)
|
while (!stop_server) {
|
||||||
return -1; // ignore child threads, as their stats are the same as the
|
sleep(3); // Wait for the stop signal
|
||||||
// parent
|
|
||||||
|
|
||||||
proc->resident = 1024 * findAndParseField(&filedata, "VmRSS");
|
|
||||||
proc->swap = 1024 * findAndParseField(&filedata, "VmSwap");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int parseIOFile(Process *proc, char *filedata) {
|
|
||||||
proc->read = findAndParseField(&filedata, "read_bytes");
|
|
||||||
proc->written = findAndParseField(&filedata, "write_bytes");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void linkFamily(Process *head) {
|
|
||||||
int orphans = 1;
|
|
||||||
|
|
||||||
while (orphans) {
|
|
||||||
orphans = 0;
|
|
||||||
Process *current = head;
|
|
||||||
// while on an actual process node, and it doesn't have a parent link, and should have a parent link
|
|
||||||
while (current != NULL && (current->parent != NULL || current->ppid <= 0)) current = current->next;
|
|
||||||
if (current == NULL) break;
|
|
||||||
orphans = 1;
|
|
||||||
|
|
||||||
Process *parent = head;
|
|
||||||
while (parent != NULL && parent->pid != current->ppid) parent = parent->next;
|
|
||||||
if (parent == NULL) {
|
|
||||||
printf("Found orphan process: %d\n", current->pid);
|
|
||||||
current->ppid = -1;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have a parent and a child ready to be united
|
// Connect to the server's listening port to unblock accept()
|
||||||
current->parent = parent;
|
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
Process **placement = &(parent->child);
|
if (sock_fd < 0) {
|
||||||
while ((*placement) != NULL) placement = &((*placement)->sibling);
|
perror("self-connector socket");
|
||||||
*placement = current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int resetVisits(Process *head) {
|
|
||||||
Process *current = head;
|
|
||||||
int processCount = 0;
|
|
||||||
|
|
||||||
while (current != NULL) {
|
|
||||||
current->visited = 0;
|
|
||||||
current = current->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
return processCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
void printFamilyTree(Process *head) {
|
|
||||||
int processCount = resetVisits(head);
|
|
||||||
int visited = 0;
|
|
||||||
Process *current = head;
|
|
||||||
int depth = 0;
|
|
||||||
|
|
||||||
while (visited < processCount) {
|
|
||||||
if (current == NULL) {
|
|
||||||
printf("Bad visit count: %d visited of %d processes\n", visited, processCount);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
current->visited = 1;
|
|
||||||
printf("%10d ", current->pid);
|
|
||||||
// `ps f` style tree
|
|
||||||
for (int i=1; i<depth; i++) puts(" ");
|
|
||||||
if (depth > 0) puts(" \\_ ");
|
|
||||||
printf("%s\n", current->label);
|
|
||||||
|
|
||||||
if (current->child != NULL) {
|
|
||||||
// process children first
|
|
||||||
depth++;
|
|
||||||
current = current->child;
|
|
||||||
} else if (current->sibling != NULL) {
|
|
||||||
// process siblings next
|
|
||||||
current = current->sibling;
|
|
||||||
} else if (current->parent != NULL) {
|
|
||||||
// return to parent when tree is exhausted
|
|
||||||
depth--;
|
|
||||||
current = current->parent;
|
|
||||||
} else {
|
|
||||||
// no parent - scan for unvisited process
|
|
||||||
while (current != NULL && current->visited == 1) current = current->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void aggregateStats(Process *head) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
char *readProcesses(char *procdir) {
|
|
||||||
int len;
|
|
||||||
struct dirent *pDirent;
|
|
||||||
DIR *pDir;
|
|
||||||
|
|
||||||
// Ensure we can open directory.
|
|
||||||
|
|
||||||
pDir = opendir(procdir);
|
|
||||||
if (pDir == NULL) {
|
|
||||||
printf("Cannot open directory '%s'\n", procdir);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process each entry.
|
struct sockaddr_in server_address = {
|
||||||
|
.sin_family = AF_INET,
|
||||||
|
.sin_port = htons(port),
|
||||||
|
.sin_addr.s_addr = htonl(INADDR_LOOPBACK), // Connect to localhost
|
||||||
|
};
|
||||||
|
|
||||||
char *buffer = malloc(4096);
|
connect(sock_fd, (struct sockaddr *)&server_address, sizeof(server_address));
|
||||||
char *fname = malloc(1024);
|
sleep(1);
|
||||||
char first;
|
close(sock_fd);
|
||||||
FILE *file;
|
return NULL;
|
||||||
|
|
||||||
Process *cur = calloc(1, sizeof(Process));
|
|
||||||
Process *head = cur;
|
|
||||||
lxcinfo *lxcs = NULL;
|
|
||||||
|
|
||||||
while ((pDirent = readdir(pDir)) != NULL) {
|
|
||||||
first = pDirent->d_name[0];
|
|
||||||
if (first < '0' || first > '9') continue;
|
|
||||||
sprintf(fname, "/proc/%s/status", pDirent->d_name);
|
|
||||||
file = fopen(fname, "rb");
|
|
||||||
if (file == NULL) continue;
|
|
||||||
fread(buffer, 1, 4096, file);
|
|
||||||
fclose(file);
|
|
||||||
|
|
||||||
if (parseStatusFile(cur, buffer)) continue;
|
|
||||||
|
|
||||||
// truncate 'status' to 'stat'
|
|
||||||
len = strlen(fname);
|
|
||||||
fname[len - 2] = 0;
|
|
||||||
file = fopen(fname, "rb");
|
|
||||||
if (file == NULL) continue;
|
|
||||||
fread(buffer, 1, 4096, file);
|
|
||||||
fclose(file);
|
|
||||||
|
|
||||||
if (parseStatFile(cur, buffer)) continue;
|
|
||||||
|
|
||||||
sprintf(fname, "/proc/%s/io", pDirent->d_name);
|
|
||||||
file = fopen(fname, "rb");
|
|
||||||
if (file == NULL) continue;
|
|
||||||
fread(buffer, 1, 4096, file);
|
|
||||||
fclose(file);
|
|
||||||
|
|
||||||
if (parseIOFile(cur, buffer)) continue;
|
|
||||||
|
|
||||||
sprintf(fname, "/proc/%s/cpuset", pDirent->d_name);
|
|
||||||
file = fopen(fname, "rb");
|
|
||||||
if (file == NULL) goto nextProcess;
|
|
||||||
fread(cur->cpuset, 1, sizeof(cur->cpuset), file);
|
|
||||||
fclose(file);
|
|
||||||
|
|
||||||
strtok(cur->cpuset, "\n\t ");
|
|
||||||
char *lxcTag = "/lxc/";
|
|
||||||
if (memcmp(cur->cpuset, lxcTag, strlen(lxcTag)) == 0) {
|
|
||||||
// Resides in LXC -- read file and tag
|
|
||||||
sscanf(cur->cpuset, "/lxc/%d/ns", &cur->lxc);
|
|
||||||
if (getLXCInfo(&lxcs, cur->lxc) == NULL)
|
|
||||||
printf("Failed to read LXC config <%d>\n", cur->lxc);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
cur->lxc = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
nextProcess:
|
|
||||||
cur->next = calloc(1, sizeof(Process));
|
|
||||||
cur->next->prev = cur;
|
|
||||||
cur = cur->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clean up unused last node
|
|
||||||
cur = cur->prev;
|
|
||||||
free(cur->next);
|
|
||||||
cur->next = NULL;
|
|
||||||
|
|
||||||
linkFamily(head);
|
|
||||||
printFamilyTree(head);
|
|
||||||
|
|
||||||
int clocks = sysconf(_SC_CLK_TCK);
|
|
||||||
char *output = malloc(8 * 1024 * 1024);
|
|
||||||
char *ptr = output;
|
|
||||||
|
|
||||||
cur = head;
|
|
||||||
while (cur != NULL) {
|
|
||||||
// create process descriptor
|
|
||||||
sprintf(buffer,
|
|
||||||
"pid=\"%d\",ppid=\"%d\",label=\"%s\",lxc=\"%d\"",
|
|
||||||
cur->pid, cur->ppid, cur->label, cur->lxc);
|
|
||||||
ptr += sprintf(ptr, "process_cpu_time_seconds{%s} %f\n", buffer, (double) cur->cpuTime / clocks);
|
|
||||||
ptr += sprintf(ptr, "process_child_cpu_time_seconds{%s} %f\n", buffer,(double) cur->childCpuTime / clocks);
|
|
||||||
ptr += sprintf(ptr, "process_num_threads{%s} %llu\n", buffer, cur->nThreads);
|
|
||||||
ptr += sprintf(ptr, "process_resident_bytes{%s} %llu\n", buffer, cur->resident);
|
|
||||||
ptr += sprintf(ptr, "process_swap_bytes{%s} %llu\n", buffer, cur->swap);
|
|
||||||
ptr += sprintf(ptr, "process_fileio_bytes_written{%s} %llu\n", buffer, cur->written);
|
|
||||||
ptr += sprintf(ptr, "process_fileio_bytes_read{%s} %llu\n", buffer, cur->read);
|
|
||||||
ptr += sprintf(ptr, "process_is_kernel_process{%s} %d\n", buffer, cur->iskernel);
|
|
||||||
ptr += sprintf(ptr, "process_tree_depth{%s} %d\n", buffer, cur->level);
|
|
||||||
// free and proceed
|
|
||||||
Process *prev = cur;
|
|
||||||
cur = cur->next;
|
|
||||||
free(prev);
|
|
||||||
}
|
|
||||||
|
|
||||||
lxcinfo *lxc = lxcs;
|
|
||||||
while (lxc != NULL) {
|
|
||||||
sprintf(buffer, "lxc=\"%d\",lxcname=\"%s\"", lxc->lxcid, lxc->hostname);
|
|
||||||
ptr += sprintf(ptr, "lxc_cpu_core_count{%s} %u\n", buffer, lxc->cpucount);
|
|
||||||
ptr += sprintf(ptr, "lxc_memory_limit_bytes{%s} %llu\n", buffer, lxc->memlimit);
|
|
||||||
ptr += sprintf(ptr, "lxc_swap_limit_bytes{%s} %llu\n", buffer, lxc->swaplimit);
|
|
||||||
// free and proceed
|
|
||||||
lxcinfo *prev = lxc;
|
|
||||||
lxc = lxc->next;
|
|
||||||
free(prev);
|
|
||||||
}
|
|
||||||
|
|
||||||
closedir(pDir);
|
|
||||||
free(buffer);
|
|
||||||
free(fname);
|
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define BUFFER_SIZE 1024
|
#define BUFFER_SIZE 1024
|
||||||
@ -342,25 +86,36 @@ void handle_client(int client_socket) {
|
|||||||
closeConnection:
|
closeConnection:
|
||||||
printf("Closing connection\n");
|
printf("Closing connection\n");
|
||||||
// Close the client socket
|
// Close the client socket
|
||||||
shutdown(client_socket, SHUT_WR);
|
usleep(100000);
|
||||||
sleep(1);
|
|
||||||
close(client_socket);
|
close(client_socket);
|
||||||
|
|
||||||
free(data);
|
free(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
int port = 9101;
|
|
||||||
|
|
||||||
if (argc == 1) {
|
if (argc == 1) {
|
||||||
char *buf = readProcesses("/proc");
|
char *buf = readProcesses("/proc");
|
||||||
if (buf == NULL) return 4;
|
if (buf == NULL) return 1;
|
||||||
printf(buf);
|
printf(buf);
|
||||||
free(buf);
|
free(buf);
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
for (int i=0; i<strlen(argv[1]); i++) {
|
||||||
port = atoi(argv[1]);
|
if (argv[1][i] < '0' || argv[1][i] > '9') {
|
||||||
|
fprintf(stderr, "Argument '%s' is not a valid port", argv[1]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int port = atoi(argv[1]);
|
||||||
|
// Set up signal handling
|
||||||
|
struct sigaction sa;
|
||||||
|
sa.sa_handler = handle_signal;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
sa.sa_flags = 0;
|
||||||
|
if (sigaction(SIGINT, &sa, NULL) == -1 || sigaction(SIGTERM, &sa, NULL) == -1) {
|
||||||
|
perror("sigaction");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
int server_socket, client_socket;
|
int server_socket, client_socket;
|
||||||
@ -393,12 +148,19 @@ int main(int argc, char *argv[]) {
|
|||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Server is listening on port %d\n", port);
|
// Start the self-connector thread
|
||||||
|
pthread_t connector_thread;
|
||||||
|
pthread_create(&connector_thread, NULL, self_connector_thread, (void *) &port);
|
||||||
|
|
||||||
|
printf("Server is listening on port %d (PID %d)\n", port, getpid());
|
||||||
|
|
||||||
// Accept and handle incoming connections
|
// Accept and handle incoming connections
|
||||||
while (1) {
|
while (1) {
|
||||||
if ((client_socket = accept(server_socket, (struct sockaddr *)&client_addr,
|
client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_len);
|
||||||
&addr_len)) == -1) {
|
printf("Got client connection\n");
|
||||||
|
if (stop_server) break; // stop if we received SIGTERM or SIGINT
|
||||||
|
|
||||||
|
if (client_socket == -1) {
|
||||||
perror("Accept failed");
|
perror("Accept failed");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -407,8 +169,10 @@ int main(int argc, char *argv[]) {
|
|||||||
handle_client(client_socket);
|
handle_client(client_socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the server socket (this won't actually be reached in this example)
|
close(client_socket);
|
||||||
close(server_socket);
|
close(server_socket);
|
||||||
|
|
||||||
|
printf("Shutting down\n");
|
||||||
|
pthread_join(connector_thread, NULL);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user