Separate LXC info fields

This commit is contained in:
Dan Snyder 2025-01-14 12:29:15 -05:00 committed by dan
parent 7901ad145d
commit b7e1868f24
7 changed files with 412 additions and 318 deletions

View File

@ -1,5 +1,6 @@
{ {
"files.associations": { "files.associations": {
"stdlib.h": "c" "stdlib.h": "c",
"process.h": "c"
} }
} }

75
src/lxcstat.c Normal file
View File

@ -0,0 +1,75 @@
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include "lxcstat.h"
#include "parsing.h"
int parseLxcConfigFile(lxcinfo *lxc, char *filedata) {
lxc->cpucount = 0;
lxc->memlimit = 0;
char *temp = filedata;
lxc->cpucount = findAndParseField(&temp, "cores");
temp = filedata;
lxc->memlimit = findAndParseField(&temp, "memory") * 1024 * 1024;
char *ptr = strstr(filedata, "hostname:");
ptr += strlen("hostname:");
while (*ptr == ' ') ptr++;
strtok(ptr, "\n\t ");
strcpy(lxc->hostname, ptr);
}
int readLxcConfig(lxcinfo *lxc, int lxcid) {
char *fname = malloc(1024);
char *buffer = malloc(4096);
sprintf(fname, "/etc/pve/lxc/%d.conf", lxcid);
FILE *file = fopen(fname, "rb");
if (file == NULL) {
printf("Failed to open <%s>: %d\n", fname, errno);
return -1;
}
fread(buffer, 1, 4096, file);
fclose(file);
parseLxcConfigFile(lxc, buffer);
free(buffer);
free(fname);
return 0;
}
lxcinfo *getLXCInfo(lxcinfo **lxcs, int lxcid) {
lxcinfo *lxc = *lxcs;
while (lxc != NULL) {
if (lxc->lxcid == lxcid) return lxc;
lxc = lxc->next;
}
lxc = malloc(sizeof(lxcinfo));
lxc->lxcid = lxcid;
if (readLxcConfig(lxc, lxcid)) {
// failed to read LXC
free(lxc);
return NULL;
}
if (*lxcs == NULL) {
// head was NULL - first LXC
*lxcs = lxc;
lxc->head = lxc;
lxc->tail = lxc;
lxc->next = NULL;
} else {
// other LXCs exist - chain to tail
(*lxcs)->tail->next = lxc;
(*lxcs)->tail = lxc;
lxc->head = *lxcs;
lxc->next = NULL;
}
return lxc;
}

16
src/lxcstat.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef LXCSTAT_H
#define LXCSTAT_H
typedef struct _lxc_info {
struct _lxc_info *next;
struct _lxc_info *head;
struct _lxc_info *tail; // only valid for head lxcinfo
int lxcid;
int cpucount;
int memlimit;
char hostname[128];
} lxcinfo;
lxcinfo *getLXCInfo(lxcinfo **lxcs, int lxcid);
#endif

44
src/parsing.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef PARSING_H
#define PARSING_H
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
static inline uint64_t fast_str2ull(char **str) {
uint64_t result = 0;
int maxlen = 20; // length of maximum value of 18446744073709551615
while (maxlen-- && **str >= '0' && **str <= '9') {
result *= 10;
result += **str - '0';
(*str)++;
}
return result;
}
static inline int64_t fast_str2ll(char **str) {
bool neg = false;
if (**str == '-') {
neg = true;
(*str)++;
}
uint64_t res = fast_str2ull(str);
int64_t result = (int64_t)res;
return neg ? -result : result;
}
static inline uint64_t findAndParseField(char **filedata, char *fld) {
char *field = strstr(*filedata, fld);
if (!field) return -1;
while (*field < '0' || *field > '9') field++;
*filedata = field;
uint64_t val = fast_str2ull(filedata);
return val;
}
#endif

View File

@ -8,17 +8,16 @@ typedef struct st_process {
struct st_process *next; struct st_process *next;
struct st_process *prev; struct st_process *prev;
uint32_t flags; uint32_t flags;
char cpuset[128];
int tgid;
// attributes identifying thread // attributes identifying thread
int lxc; int lxc;
int pid; int pid;
int ppid; int ppid;
int tgid;
int level; int level;
char label[128]; char label[128];
char cpuset[128];
char lxcname[128];
int iskernel;
// statistics - each outputs 1 line per process // statistics - each outputs 1 line per process
int iskernel;
uint64_t cpuTime; uint64_t cpuTime;
uint64_t childCpuTime; uint64_t childCpuTime;
uint64_t nThreads; uint64_t nThreads;

View File

@ -1,12 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
int main3(int argc, char *argv[]) {
DIR *procdir = opendir("/proc");
struct dirent *pDirent;
while ((pDirent = readdir(procdir)) != NULL) {
printf("[%s]\n", pDirent->d_name);
}
}

View File

@ -1,362 +1,333 @@
#include <arpa/inet.h>
#include <dirent.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h>
#include <string.h> #include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#include <stdint.h>
#include <dirent.h>
#include "process.h" #include "process.h"
#include "lxcstat.h"
#include "parsing.h"
#ifndef PF_KTHREAD #ifndef PF_KTHREAD
# define PF_KTHREAD 0x00200000 #define PF_KTHREAD 0x00200000
#endif #endif
static inline uint64_t fast_str2ull(char** str) { #define skipRange(inclusive, exclusive) \
uint64_t result = 0; for (int i##inclusive = 0; i##inclusive < exclusive - inclusive; \
int maxlen = 20; // length of maximum value of 18446744073709551615 i##inclusive++) \
location = strchr(location, ' ') + 1;
while (maxlen-- && **str >= '0' && **str <= '9') {
result *= 10;
result += **str - '0';
(*str)++;
}
return result;
}
static inline int64_t fast_str2ll(char** str) {
bool neg = false;
if (**str == '-') {
neg = true;
(*str)++;
}
uint64_t res = fast_str2ull(str);
int64_t result = (int64_t) res;
return neg ? -result : result;
}
#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) { int parseStatFile(Process *proc, char *filedata) {
char *location = filedata; char *location = filedata;
// (1) pid - %d // (1) pid - %d
proc->pid = fast_str2ull(&location); proc->pid = fast_str2ull(&location);
location += 2; location += 2;
// (2) comm - %s // (2) comm - %s
char *end = strrchr(location, ')'); char *end = strrchr(location, ')');
size_t length = end - location; size_t length = end - location;
memcpy(proc->label, location, length); memcpy(proc->label, location, length);
proc->label[length] = 0; proc->label[length] = 0;
location = end + 2; location = end + 2;
// (3) State - %c // (3) State - %c
location += 2; location += 2;
// (4) ppid - %d // (4) ppid - %d
proc->ppid = fast_str2ull(&location); proc->ppid = fast_str2ull(&location);
location += 1; location += 1;
// skip [5 - 9) // skip [5 - 9)
skipRange(5, 9); skipRange(5, 9);
// (9) flags - %u // (9) flags - %u
proc->flags = fast_str2ull(&location); proc->flags = fast_str2ull(&location);
location += 1; location += 1;
proc->iskernel = (proc->flags & PF_KTHREAD) ? true : false; proc->iskernel = (proc->flags & PF_KTHREAD) ? true : false;
// skip [10 - 14) // skip [10 - 14)
skipRange(10, 14); skipRange(10, 14);
// (14) utime - %lu // (14) utime - %lu
proc->cpuTime = fast_str2ull(&location); proc->cpuTime = fast_str2ull(&location);
location += 1; location += 1;
// (15) stime - %lu // (15) stime - %lu
proc->cpuTime += fast_str2ull(&location); proc->cpuTime += fast_str2ull(&location);
location += 1; location += 1;
// (16) cutime - %lu // (16) cutime - %lu
proc->childCpuTime = fast_str2ull(&location); proc->childCpuTime = fast_str2ull(&location);
location += 1; location += 1;
// (17) cstime - %lu // (17) cstime - %lu
proc->childCpuTime += fast_str2ull(&location); proc->childCpuTime += fast_str2ull(&location);
location += 1; location += 1;
// skip 18-19 // skip 18-19
skipRange(18, 20); skipRange(18, 20);
// (20) num_threads - %ld // (20) num_threads - %ld
proc->nThreads = fast_str2ull(&location); proc->nThreads = fast_str2ull(&location);
location += 1; location += 1;
return 0; return 0;
}
static inline uint64_t findAndParseField(char **filedata, char *fld) {
char *field = strstr(*filedata, fld);
if (!field) return -1;
while (*field < '0' || *field > '9') field++;
*filedata = field;
uint64_t val = fast_str2ull(filedata);
return val;
} }
int parseStatusFile(Process *proc, char *filedata) { int parseStatusFile(Process *proc, char *filedata) {
proc->tgid = findAndParseField(&filedata, "Tgid"); proc->tgid = findAndParseField(&filedata, "Tgid");
proc->pid = findAndParseField(&filedata, "Pid"); proc->pid = findAndParseField(&filedata, "Pid");
if (proc->pid != proc->tgid) return -1; // ignore child threads, as their stats are the same as the parent 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->resident = 1024 * findAndParseField(&filedata, "VmRSS");
proc->swap = 1024 * findAndParseField(&filedata, "VmSwap"); proc->swap = 1024 * findAndParseField(&filedata, "VmSwap");
return 0; return 0;
} }
int parseIOFile(Process *proc, char *filedata) { int parseIOFile(Process *proc, char *filedata) {
proc->read = findAndParseField(&filedata, "read_bytes"); proc->read = findAndParseField(&filedata, "read_bytes");
proc->written = findAndParseField(&filedata, "write_bytes"); proc->written = findAndParseField(&filedata, "write_bytes");
return 0; return 0;
} }
char *readProcesses(char *procdir) { char *readProcesses(char *procdir) {
int len; int len;
struct dirent *pDirent; struct dirent *pDirent;
DIR *pDir; DIR *pDir;
// Ensure we can open directory. // Ensure we can open directory.
pDir = opendir(procdir); pDir = opendir(procdir);
if (pDir == NULL) { if (pDir == NULL) {
printf("Cannot open directory '%s'\n", procdir); printf("Cannot open directory '%s'\n", procdir);
return NULL; return NULL;
}
// Process each entry.
char *buffer = malloc(4096);
char *fname = malloc(1024);
char first;
FILE *file;
Process *cur = malloc(sizeof(Process));
cur->prev = NULL;
cur->next = NULL;
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;
} }
// Process each entry. nextProcess:
cur->next = malloc(sizeof(Process));
cur->next->prev = cur;
cur = cur->next;
}
char *buffer = malloc(4096); // clean up unused last node
char *fname = malloc(1024); cur = cur->prev;
char first; free(cur->next);
FILE *file; cur->next = NULL;
Process *cur = malloc(sizeof(Process)); int clocks = sysconf(_SC_CLK_TCK);
cur->prev = NULL; char *output = malloc(8 * 1024 * 1024);
cur->next = NULL; char *ptr = output;
Process *head = cur;
while ((pDirent = readdir(pDir)) != NULL) { cur = head;
first = pDirent->d_name[0]; while (cur != NULL) {
if (first < '0' || first > '9') continue; // create process descriptor
sprintf(fname, "/proc/%s/status", pDirent->d_name); sprintf(buffer,
file = fopen(fname, "rb"); "pid=\"%d\",ppid=\"%d\",label=\"%s\",lxc=\"%d\",level=\"%d\"",
if (file == NULL) continue; cur->pid, cur->ppid, cur->label, cur->lxc, cur->level);
fread(buffer, 1, 4096, file); ptr += sprintf(ptr, "process_cpu_time_seconds{%s} %f\n", buffer, (double) cur->cpuTime / clocks);
fclose(file); 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);
// free and proceed
Process *prev = cur;
cur = cur->next;
free(prev);
}
if (parseStatusFile(cur, buffer)) continue; 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);
// free and proceed
lxcinfo *prev = lxc;
lxc = lxc->next;
free(prev);
}
// truncate 'status' to 'stat' closedir(pDir);
len = strlen(fname); free(buffer);
fname[len-2] = 0; free(fname);
file = fopen(fname, "rb"); return output;
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);
sprintf(fname, "/etc/pve/lxc/%d.conf", cur->lxc);
file = fopen(fname, "rb");
if (file == NULL) goto nextProcess;
fread(buffer, 1, 4096, file);
fclose(file);
char *ptr = strstr(buffer, "hostname:");
ptr += strlen("hostname:");
while (*ptr == ' ') ptr++;
strtok(ptr, "\n\t ");
strcpy(cur->lxcname, ptr);
} else {
cur->lxcname[0] = 0;
cur->lxc = -1;
}
nextProcess:
cur->next = malloc(sizeof(Process));
cur->next->prev = cur;
cur = cur->next;
}
// clean up unused last node
cur = cur->prev;
free(cur->next);
cur->next = NULL;
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\",kernel=\"%d\",ctid=\"%d\",lxc=\"%s\"", cur->pid, cur->ppid, cur->label, cur->iskernel, cur->lxc, cur->lxcname);
ptr += sprintf(ptr, "proc_cpu_time{%s} %f\n", buffer, (double) cur->cpuTime / clocks);
ptr += sprintf(ptr, "proc_child_cpu_time{%s} %f\n", buffer, (double) cur->childCpuTime / clocks);
ptr += sprintf(ptr, "proc_num_threads{%s} %llu\n", buffer, cur->nThreads);
ptr += sprintf(ptr, "proc_resident_bytes{%s} %llu\n", buffer, cur->resident);
ptr += sprintf(ptr, "proc_swap_bytes{%s} %llu\n", buffer, cur->swap);
ptr += sprintf(ptr, "proc_fileio_bytes_written{%s} %llu\n", buffer, cur->written);
ptr += sprintf(ptr, "proc_fileio_bytes_read{%s} %llu\n", buffer, cur->read);
cur = cur->next;
}
cur = head;
while (cur != NULL) {
Process *prev = cur;
cur = cur->next;
free(prev);
}
closedir(pDir);
free(buffer);
free(fname);
return output;
} }
#define BUFFER_SIZE 1024 #define BUFFER_SIZE 1024
void handle_client(int client_socket) { void handle_client(int client_socket) {
char *data = readProcesses("/proc"); char *data = readProcesses("/proc");
int length = strlen(data); int length = strlen(data);
printf("Got data of length: %d\n", length); printf("Got data of length: %d\n", length);
// HTTP response // HTTP response
const char *http_headers_fmt = const char *http_headers_fmt =
"HTTP/1.1 200 OK\r\n" "HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n" "Content-Type: text/plain\r\n"
"Content-Length: %u\r\n" "Content-Length: %u\r\n"
"Connection: close\r\n" "Connection: close\r\n"
"\r\n"; "\r\n";
char headers[BUFFER_SIZE]; char headers[BUFFER_SIZE];
sprintf(headers, http_headers_fmt, length); sprintf(headers, http_headers_fmt, length);
printf("Sending headers\n");
if (send(client_socket, headers, strlen(headers), 0) == -1) {
printf("Failed to send headers\n");
free(data);
return;
} else {
printf("Sent headers\n");
}
// Send the response to the client
int total_sent = 0;
while (total_sent < length) {
int amt = length - total_sent;
int sent = send(client_socket, data+total_sent, amt, 0);
printf("Tried sending %d actually sent %d\n", amt, sent);
total_sent += sent;
}
printf("Closing connection\n");
// Close the client socket
shutdown(client_socket, SHUT_WR);
sleep(1);
close(client_socket);
printf("Sending headers\n");
if (send(client_socket, headers, strlen(headers), 0) == -1) {
printf("Failed to send headers\n");
free(data); free(data);
return;
} else {
printf("Sent headers\n");
}
// Send the response to the client
int total_sent = 0;
while (total_sent < length) {
int amt = length - total_sent;
int sent = send(client_socket, data + total_sent, amt, 0);
printf("Tried sending %d actually sent %d\n", amt, sent);
total_sent += sent;
}
printf("Closing connection\n");
// Close the client socket
shutdown(client_socket, SHUT_WR);
sleep(1);
close(client_socket);
free(data);
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
int port = 9101;
int port = 9101; if (argc == 1) {
char *buf = readProcesses("/proc");
if (argc == 1) { if (buf == NULL) return 4;
char *buf = readProcesses("/proc"); printf(buf);
if (buf == NULL) return 4;
printf(buf);
return 0;
} else {
port = atoi(argv[1]);
}
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
// Create the server socket
if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Configure the server address
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
// Bind the socket to the specified port
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("Bind failed");
close(server_socket);
exit(EXIT_FAILURE);
}
// Listen for incoming connections
if (listen(server_socket, 5) == -1) {
perror("Listen failed");
close(server_socket);
exit(EXIT_FAILURE);
}
printf("Server is listening on port %d\n", port);
// Accept and handle incoming connections
while (1) {
if ((client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_len)) == -1) {
perror("Accept failed");
continue;
}
// Handle the client in a separate function
handle_client(client_socket);
}
// Close the server socket (this won't actually be reached in this example)
close(server_socket);
return 0; return 0;
} else {
port = atoi(argv[1]);
}
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
// Create the server socket
if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Configure the server address
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
// Bind the socket to the specified port
if (bind(server_socket, (struct sockaddr *)&server_addr,
sizeof(server_addr)) == -1) {
perror("Bind failed");
close(server_socket);
exit(EXIT_FAILURE);
}
// Listen for incoming connections
if (listen(server_socket, 5) == -1) {
perror("Listen failed");
close(server_socket);
exit(EXIT_FAILURE);
}
printf("Server is listening on port %d\n", port);
// Accept and handle incoming connections
while (1) {
if ((client_socket = accept(server_socket, (struct sockaddr *)&client_addr,
&addr_len)) == -1) {
perror("Accept failed");
continue;
}
// Handle the client in a separate function
handle_client(client_socket);
}
// Close the server socket (this won't actually be reached in this example)
close(server_socket);
return 0;
} }