#include #include #include #include #include #include #include #include #include #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); proc->cCpuTime = proc->cpuTime; 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) { 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; } 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 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 current->parent = parent; Process **placement = &(parent->child); while ((*placement) != NULL) placement = &((*placement)->sibling); *placement = current; } } int resetVisits(Process *head) { Process *current = head; int processCount = 0; while (current != NULL) { current->visited = 0; current = current->next; processCount++; } 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; visited++; printf("%10d %10d %1d ", current->pid, current->ppid, current->iskernel); // `ps f` style tree for (int i=1; i 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 == 1) 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 == 1) 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->cCpuTime; 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 *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); aggregateStats(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); // 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); // free and proceed lxcinfo *prev = lxc; lxc = lxc->next; free(prev); } closedir(pDir); free(buffer); free(fname); return output; } #define BUFFER_SIZE 1024 void handle_client(int client_socket) { char *data = readProcesses("/proc"); int length = strlen(data); printf("Got data of length: %d\n", length); // HTTP response const char *http_headers_fmt = "HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" "Content-Length: %u\r\n" "Connection: close\r\n" "\r\n"; char headers[BUFFER_SIZE]; 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); if (sent == -1) { printf("Failed to complete response\n"); goto closeConnection; } total_sent += sent; } closeConnection: 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 port = 9101; if (argc == 1) { char *buf = readProcesses("/proc"); if (buf == NULL) return 4; printf(buf); free(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; }