├── http ├── favicon.ico ├── help.html ├── css │ └── atop.css ├── index.html ├── js │ ├── atop_compare_fc.js │ ├── atop.js │ └── atop_parse.js └── template │ └── html │ ├── command_line.html │ ├── disk.html │ ├── container.html │ ├── generic.html │ └── memory.html ├── .gitmodules ├── CONTRIBUTING.md ├── atophttpd.service ├── config.h ├── json.h ├── packaging └── debian │ ├── control │ └── makedeb.sh ├── .gitignore ├── Makefile ├── output.h ├── cache.h ├── httpd.h ├── man └── atophttpd.1 ├── bench.py ├── output.c ├── gen-cert.sh ├── README.md ├── connection.h ├── socket.c ├── connection.c ├── cache.c ├── tls.c ├── rawlog.c ├── LICENSE ├── httpd.c └── json.c /http/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pizhenwei/atophttpd/HEAD/http/favicon.ico -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "atop"] 2 | path = atop 3 | url = https://github.com/Atoptool/atop.git 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Maintainer 2 | - zhenwei pi (HTTP backend) 3 | - Enhua Zhou (HTTP frontend) 4 | 5 | # Patch is always welcomed! 6 | - Sending patch to maintainer by email 7 | - Create a PR on github 8 | -------------------------------------------------------------------------------- /atophttpd.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=A web style atop(https://www.atoptool.nl) 3 | Documentation=man:atophttpd(1) 4 | Requires=atop.service 5 | 6 | [Service] 7 | Type=simple 8 | Environment="PORT=2867" 9 | Environment="LOGPATH=/var/log/atop" 10 | ExecStart=/bin/sh -c 'exec /usr/bin/atophttpd -P ${LOGPATH} -p ${PORT}' 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 zhenwei pi 3 | * 4 | * Authors: 5 | * zhenwei pi 6 | * 7 | * This work is licensed under the terms of the GNU GPL, version 2 or later. 8 | * See the COPYING file in the top-level directory. 9 | */ 10 | 11 | #ifndef _CONFIG_H_ 12 | #define _CONFIG_H_ 13 | 14 | typedef long long count_t; 15 | extern unsigned int pagesize; 16 | extern unsigned short hertz; 17 | extern struct utsname utsname; 18 | extern unsigned int hidecmdline; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /json.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 zhenwei pi 3 | * 4 | * Authors: 5 | * zhenwei pi 6 | * Fei Li 7 | * 8 | * This work is licensed under the terms of the GNU GPL, version 2 or later. 9 | * See the COPYING file in the top-level directory. 10 | */ 11 | 12 | #ifndef _JSON_H_ 13 | #define _JSON_H_ 14 | 15 | #include "photosyst.h" 16 | #include "photoproc.h" 17 | #include "connection.h" 18 | 19 | int jsonout(int, char *, time_t, int, struct devtstat *, struct sstat *, int, unsigned int, char, connection* connection); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /packaging/debian/control: -------------------------------------------------------------------------------- 1 | Package: atophttpd 2 | Version: ATOP_VERSION 3 | Architecture: amd64 4 | Maintainer: zhenwei pi 5 | Installed-Size: ATOPHTTPD_SIZE 6 | Depends: atop (= ATOP_VERSION) 7 | Section: admin 8 | Priority: optional 9 | Homepage: https://github.com/pizhenwei/atophttpd 10 | Description: A web style atop(https://www.atoptool.nl). 11 | atophttpd depends on atop and reads atop rawlog, provides web 12 | service(HTTP 1.1). A user accesses atop by a web browser without 13 | login permission of a server, it's also possible to get/analysis 14 | statistics of many servers in batch. 15 | . 16 | Author: zhenwei pi Enhua Zhou 17 | -------------------------------------------------------------------------------- /packaging/debian/makedeb.sh: -------------------------------------------------------------------------------- 1 | ROOT_PATH=$1 2 | PACKAGE_PATH=$ROOT_PATH/packaging/debian/deb 3 | CONTROL_PATH=$ROOT_PATH/packaging/debian/deb/DEBIAN 4 | 5 | mkdir -p $CONTROL_PATH 6 | make -C $ROOT_PATH install prefix=$PACKAGE_PATH 7 | 8 | cp $ROOT_PATH/packaging/debian/control $CONTROL_PATH 9 | # replace version info 10 | ATOP_VERSION=`grep ATOPVERS atop/version.h | tr "\"" " " | awk '{print $3}'` 11 | sed -i "s/ATOP_VERSION/$ATOP_VERSION/" $CONTROL_PATH/control 12 | 13 | # replace package size info 14 | ATOPHTTPD_SIZE=`du -k -d 0 $PACKAGE_PATH | awk '{print $1}'` 15 | sed -i "s/ATOPHTTPD_SIZE/$ATOPHTTPD_SIZE/" $CONTROL_PATH/control 16 | 17 | dpkg-deb -b $PACKAGE_PATH $ROOT_PATH/atophttpd_$ATOP_VERSION.deb 18 | 19 | # cleanup 20 | rm -rf $PACKAGE_PATH 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # target binary file 55 | atophttpd 56 | 57 | # development 58 | tags 59 | 60 | # debian package 61 | *.deb 62 | -------------------------------------------------------------------------------- /http/help.html: -------------------------------------------------------------------------------- 1 |

ATOPHTTPD HELP PAGE

2 | 3 |

Locations

4 | 5 |
    6 |
  • ping: ping and  test atophttpd service.
  • 7 |
  • help: show this help page.
  • 8 |
  • index.html: get index.html.
  • 9 |
  • favicon.ico: get favicon.ico.
  • 10 |
  • js/atop.js: get atop.js.
  • 11 |
  • css/atop.css: get css/atop.css.
  • 12 |
  • template: get template for atop rendering. Supported argument type(required, available options: generic/memory/disk/command_line).
  • 13 |
  • showsamp: get atop sample data. Supported argument timestamp(required, UNIX timestamp to query), lables(required, available options: ALL/CPU/cpu/CPL/GPU/MEM/SWP/PAG/PSI/LVM/MDD/DSK/NFM/NFC/NFS/NET/IFB/NUM/NUC/LLC/PRG/PRC/PRM/PRD/PRN/PRE. Select one lable, Ex lables=CPU; or select multiple lables, Ex lables=CPU,cpu,CPL), encoding(optional, available options: deflate/none).
  • 14 |
15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -Iatop -g -O2 -lz -Wall -Wcast-align -std=gnu11 2 | OBJS = cache.o httpd.o json.o output.o rawlog.o version.o connection.o socket.o tls.o 3 | BIN = atophttpd 4 | PREFIX := $(prefix) 5 | CC=gcc 6 | 7 | ifneq (,$(filter $(USE_TLS),yes YES y Y 1)) 8 | CFLAGS += -lssl -lcrypto -DUSE_TLS 9 | endif 10 | 11 | all: submodule bin 12 | $(CC) -o $(BIN) $(OBJS) $(CFLAGS) 13 | 14 | install: bin 15 | install -D -s $(BIN) $(PREFIX)/usr/bin/$(BIN) 16 | install -D atophttpd.service $(PREFIX)/lib/systemd/system/atophttpd.service 17 | install -D man/atophttpd.1 $(PREFIX)/usr/share/man/man1/atophttpd.1 18 | 19 | deb: bin 20 | @sh packaging/debian/makedeb.sh `pwd` 21 | 22 | bin: $(OBJS) 23 | $(CC) -o $(BIN) $(OBJS) $(CFLAGS) 24 | 25 | %.o: %.c 26 | $(CC) -c $(CFLAGS) $*.c -o $*.o 27 | 28 | version.o: 29 | @make -C atop versdate.h 30 | $(CC) -c $(CFLAGS) atop/version.c -o version.o 31 | 32 | submodule: 33 | git submodule update --init --recursive 34 | 35 | clean: 36 | @rm -f $(BIN) *.o *.deb 37 | -------------------------------------------------------------------------------- /output.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 zhenwei pi 3 | * 4 | * Authors: 5 | * zhenwei pi 6 | * 7 | * This work is licensed under the terms of the GNU GPL, version 2 or later. 8 | * See the COPYING file in the top-level directory. 9 | */ 10 | 11 | #ifndef _OUTPUT_H_ 12 | #define _OUTPUT_H_ 13 | 14 | #include "connection.h" 15 | 16 | enum { 17 | OUTPUT_STDOUT, 18 | OUTPUT_FD, 19 | OUTPUT_BUF 20 | }; 21 | 22 | struct output { 23 | int output_type; 24 | union { 25 | /* OUTPUT_STDOUT needs no more argument */ 26 | int fd; /* for OUTPUT_FD */ 27 | struct output_buf { 28 | char *buf; 29 | int size; /* size of buf, auto grow if not enough */ 30 | int offset; /* offset of buf, reset to 0 for next record */ 31 | } ob; /* OUTPUT_BUF */ 32 | }; 33 | void (*done)(struct output *op, connection *conn); 34 | char *encoding; 35 | }; 36 | 37 | void output_samp(struct output *op, char *buf, int size); 38 | void output_samp_done(struct output *op, connection *conn); 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /cache.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 zhenwei pi 3 | * 4 | * Authors: 5 | * zhenwei pi 6 | * 7 | * This work is licensed under the terms of the GNU GPL, version 2 or later. 8 | * See the COPYING file in the top-level directory. 9 | */ 10 | 11 | #ifndef _CACHE_H_ 12 | #define _CACHE_H_ 13 | 14 | #include 15 | #include 16 | 17 | struct cache_elem_t { 18 | time_t time; 19 | off_t off; 20 | }; 21 | 22 | struct cache_t { 23 | char *name; 24 | int flags; 25 | int max_elems; 26 | int nr_elems; 27 | struct cache_elem_t *elems; 28 | off_t st_size; 29 | struct timespec st_mtim; 30 | }; 31 | 32 | struct cache_t *cache_alloc(const char *name); 33 | struct cache_t *cache_find(const char *name); 34 | void cache_free(const char *name); 35 | void cache_set(struct cache_t *cache, time_t time, off_t off); 36 | struct cache_t *cache_get(time_t time, off_t *off); 37 | void cache_done(struct cache_t *cache); 38 | void cache_sort(); 39 | struct cache_t *cache_get_recent(); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /httpd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 zhenwei pi 3 | * 4 | * Authors: 5 | * zhenwei pi 6 | * 7 | * This work is licensed under the terms of the GNU GPL, version 2 or later. 8 | * See the COPYING file in the top-level directory. 9 | */ 10 | 11 | #ifndef _HTTPD_H_ 12 | #define _HTTPD_H_ 13 | 14 | extern int __debug; 15 | 16 | #include "connection.h" 17 | 18 | #define log_debug(fmt, args...) \ 19 | if (__debug) { \ 20 | fprintf(stdout, "%s[%d] " fmt, \ 21 | __func__, __LINE__, ##args); \ 22 | } 23 | 24 | int rawlog_parse_all(const char *path); 25 | int rawlog_get_record(time_t ts, char *lables, connection *conn); 26 | 27 | typedef struct atophttpd_tls_context_config { 28 | int tls_port; 29 | char *tls_addr; 30 | char *ca_cert_file; 31 | char *cert_file; 32 | char *key_file; 33 | } atophttpd_tls_context_config; 34 | 35 | struct atophttd_context { 36 | int port; 37 | int daemonmode; 38 | char *addr; 39 | char *log_path; 40 | 41 | atophttpd_tls_context_config tls_ctx_config; 42 | 43 | connection* listeners[CONN_TYPE_MAX]; 44 | }; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /man/atophttpd.1: -------------------------------------------------------------------------------- 1 | .TH ATOPHTTPD 1 "Jan 2023" "Linux" 2 | .SH NAME 3 | .B atophttpd 4 | - A web style atop(https://www.atoptool.nl) 5 | .SH SYNOPSIS 6 | .B atophttpd 7 | [\-h] [\-d] [\-D] [\-H] [-p PORT] [-P PATH] 8 | .SH DESCRIPTION 9 | .I atophttpd 10 | depends on atop and reads atop rawlog, provides web 11 | service(HTTP 1.1). A user accesses atop by a web browser without 12 | login permission of a server, it's also possible to get/analyze 13 | statistics of many servers in batch. 14 | 15 | Since atophttpd access the rawlog of atop, this daemon requires the 16 | permission of 17 | .B PATH. 18 | .SH OPTIONS 19 | .TP 20 | \-h 21 | Print usage message. 22 | .TP 23 | \-d 24 | Run in daemon mode. 25 | .TP 26 | \-D 27 | Run with debug message. (not in daemon mode) 28 | .TP 29 | \-H 30 | Hide cmdline for security protection. (Ex, mysql -pPASSWD) 31 | .TP 32 | \-p PORT 33 | Listen to PORT, default 2867. 34 | .TP 35 | \-P PATH 36 | Specify atop log path, default 37 | .B 38 | /var/log/atop. 39 | .SH SOURCE 40 | https://github.com/pizhenwei/atophttpd 41 | .SH OS 42 | Linux 43 | .SH AUTHOR 44 | zhenwei pi / Enhua Zhou 45 | .SH SEE ALSO 46 | atop(1) 47 | -------------------------------------------------------------------------------- /bench.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import requests 3 | import time 4 | 5 | url_status = {"favicon.ico": 200,\ 6 | "index.html": 200,\ 7 | "js/atop.js": 200,\ 8 | "css/atop.css": 200,\ 9 | "template?type=generic": 200,\ 10 | "template?type=memory": 200,\ 11 | "template?type=disk": 200,\ 12 | "template?type=command_line": 200,\ 13 | "showsamp": 200,\ 14 | "showsamp?lables=ALL×tamp=" + str(int(time.time())): 200,\ 15 | "notexist": 404} 16 | 17 | def bench(): 18 | for num in range(0, 100): 19 | for url, status in url_status.items(): 20 | request = 'http://127.0.0.1:2867/' + url 21 | result = requests.get(request) 22 | if result.status_code != status: 23 | sys.exit(request + " failed!") 24 | 25 | def main(): 26 | pingurl = 'http://127.0.0.1:2867/ping' 27 | ping = requests.get(pingurl) 28 | if ping.status_code != 200: 29 | sys.exit(pingurl + " failed!") 30 | 31 | bench() 32 | 33 | if __name__ == "__main__": 34 | main() 35 | -------------------------------------------------------------------------------- /http/css/atop.css: -------------------------------------------------------------------------------- 1 | button { 2 | width: 100px; 3 | height: 100px; 4 | } 5 | 6 | div.atop { 7 | min-width:1500px; 8 | font-family: monospace 9 | } 10 | 11 | div.global_header_entry { 12 | width: 25%; 13 | } 14 | 15 | div.global_header_elapsed_entry { 16 | width: 25%; 17 | text-align: right; 18 | margin-right: 2%; 19 | } 20 | 21 | div.bar { 22 | width: 99%; 23 | display: flex; 24 | margin-left: 1%; 25 | margin-right: 1%; 26 | border-style: groove; 27 | } 28 | 29 | /* HEADER */ 30 | div.header_key { 31 | width: 3%; 32 | text-align: left; 33 | display: contents; 34 | } 35 | 36 | /* 16 entry */ 37 | div.header_entry { 38 | width: 6.5%; 39 | display: flex; 40 | } 41 | 42 | div.header_entry_name { 43 | width: 50%; 44 | white-space: nowrap; 45 | text-align: left; 46 | } 47 | 48 | div.header_entry_value { 49 | width: 50%; 50 | padding-right: 1%; 51 | white-space: nowrap; 52 | text-align: right; 53 | direction: rtl; 54 | } 55 | 56 | 57 | div.header_entry_split { 58 | width: 1%; 59 | display: flex; 60 | } 61 | 62 | 63 | /* PROCESS */ 64 | div.process_entry { 65 | width: 5%; 66 | text-align: right; 67 | } 68 | 69 | div.process_last_entry { 70 | padding-left: 1%; 71 | text-align: right; 72 | } 73 | 74 | div.outer-container{ 75 | width: 100%; 76 | height: 500px; 77 | position: relative; 78 | overflow: hidden; 79 | } 80 | div.inner-container{ 81 | position: absolute; 82 | left: 0; 83 | top: 0; 84 | right: -16px; 85 | bottom: 0; 86 | overflow-y: auto; 87 | overflow-x: hidden; 88 | } 89 | 90 | 91 | -------------------------------------------------------------------------------- /output.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 zhenwei pi 3 | * 4 | * Authors: 5 | * zhenwei pi 6 | * 7 | * This work is licensed under the terms of the GNU GPL, version 2 or later. 8 | * See the COPYING file in the top-level directory. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "output.h" 18 | 19 | #define OUTBUF_DEF_SIZE (1024 * 1024) 20 | 21 | static void output_buf(struct output *op, char *buf, int size) 22 | { 23 | if (!op->ob.buf) 24 | { 25 | op->ob.size = OUTBUF_DEF_SIZE; 26 | op->ob.buf = calloc(1, op->ob.size); 27 | } 28 | 29 | /* no enought buf, grow it */ 30 | if (op->ob.size - op->ob.offset < size) 31 | { 32 | op->ob.size *= 2; 33 | op->ob.buf = realloc(op->ob.buf, op->ob.size); 34 | } 35 | 36 | memcpy(op->ob.buf + op->ob.offset, buf, size); 37 | op->ob.offset += size; 38 | } 39 | 40 | void output_samp(struct output *op, char *buf, int size) 41 | { 42 | switch (op->output_type) 43 | { 44 | case OUTPUT_STDOUT: 45 | printf("%s", buf); 46 | break; 47 | 48 | case OUTPUT_FD: 49 | write(op->fd, buf, size); 50 | break; 51 | 52 | case OUTPUT_BUF: 53 | output_buf(op, buf, size); 54 | break; 55 | 56 | default: 57 | fprintf(stderr, "Error, unknown output type\n"); 58 | exit(EINVAL); 59 | } 60 | } 61 | 62 | void output_samp_done(struct output *op, connection *conn) 63 | { 64 | if (op->done) 65 | op->done(op, conn); 66 | 67 | if (op->output_type == OUTPUT_BUF) 68 | { 69 | memset(op->ob.buf, 0x00, op->ob.offset); 70 | op->ob.offset = 0; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /http/index.html: -------------------------------------------------------------------------------- 1 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /http/js/atop_compare_fc.js: -------------------------------------------------------------------------------- 1 | function compareOrderByCpu(a, b) { 2 | acpu = a["utime"] + a["stime"]; 3 | bcpu = b["utime"] + b["stime"]; 4 | if (acpu > bcpu) { 5 | return -1; 6 | } 7 | if (acpu < bcpu) { 8 | return 1; 9 | } 10 | return 0; 11 | } 12 | 13 | function compareOrderByDisk(a, b) { 14 | if (a["wsz"] > a["cwsz"]) { 15 | adsk = a["rio"] + a["wsz"] - a["cwsz"]; 16 | } else { 17 | adsk = a["rio"]; 18 | } 19 | 20 | if (b["wsz"] > b["cwsz"]) { 21 | bdsk = b["rio"] + b["wsz"] - b["cwsz"]; 22 | } else { 23 | bdsk = b["rio"]; 24 | } 25 | 26 | if (adsk > bdsk) { 27 | return -1; 28 | } 29 | 30 | if (adsk < bdsk) { 31 | return 1; 32 | } 33 | 34 | return compareOrderByCpu(a, b); 35 | } 36 | 37 | function compareOrderByDiskIoms(a, b) { 38 | adsk_value = a["io_ms"] 39 | bdsk_value = b["io_ms"] 40 | 41 | if (adsk_value > bdsk_value) { 42 | return -1; 43 | } 44 | 45 | if (adsk_value < bdsk_value) { 46 | return 1; 47 | } 48 | 49 | return 0; 50 | } 51 | 52 | function compareOrderByMem(a, b) { 53 | amem = a["rmem"]; 54 | bmem = b["rmem"]; 55 | 56 | if (amem > bmem) { 57 | return -1; 58 | } 59 | if (amem < bmem) { 60 | return 1; 61 | } 62 | 63 | return 0; 64 | } 65 | 66 | function compareOrderByNet(a, b) { 67 | anet_value = a["rpack"] + a["spack"] 68 | bnet_value = b["rpack"] + b["bpack"] 69 | 70 | if (anet_value > bnet_value) { 71 | return -1; 72 | } 73 | 74 | if (anet_value < bnet_value) { 75 | return 1; 76 | } 77 | 78 | return 0; 79 | } 80 | 81 | function compareOrderLLC(a, b) { 82 | aLLC = a["occupancy"] * 1 83 | bLLC = b["occupancy"] * 1 84 | 85 | if (aLLC > bLLC) { 86 | return -1; 87 | } 88 | 89 | if (aLLC < bLLC) { 90 | return 1; 91 | } 92 | 93 | return 0; 94 | } 95 | -------------------------------------------------------------------------------- /http/template/html/command_line.html: -------------------------------------------------------------------------------- 1 | 26 |
27 |
PID
28 |
TID
29 |
S
30 |
DSK
31 |
COMMAND-LINE
32 |
33 | 34 |
35 |
36 |
37 |
38 |
{pid}
39 |
{tid}
40 |
{state}
41 |
{diskbusy}
42 |
{cmdline}
43 |
44 |
45 |
46 |
-------------------------------------------------------------------------------- /gen-cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Generate some test certificates which are used by the regression test suite: 4 | # 5 | # tls/ca.{crt,key} Self signed CA certificate. 6 | # tls/atophttpd.{crt,key} A certificate with no key usage/policy restrictions. 7 | # tls/client.{crt,key} A certificate restricted for SSL client usage. 8 | # tls/server.{crt,key} A certificate restricted for SSL server usage. 9 | # tls/atophttpd.dh DH Params file. 10 | 11 | generate_cert() { 12 | local name=$1 13 | local cn=$2 14 | local opts="$3" 15 | 16 | local keyfile=tls/${name}.key 17 | local certfile=tls/${name}.crt 18 | 19 | [ -f $keyfile ] || openssl genrsa -out $keyfile 2048 20 | openssl req \ 21 | -new -sha256 \ 22 | -subj "/O=Atophttpd Test/CN=$cn" \ 23 | -key $keyfile | \ 24 | openssl x509 \ 25 | -req -sha256 \ 26 | -CA tls/ca.crt \ 27 | -CAkey tls/ca.key \ 28 | -CAserial tls/ca.txt \ 29 | -CAcreateserial \ 30 | -days 365 \ 31 | $opts \ 32 | -out $certfile 33 | } 34 | 35 | read -p "Enter DNS name: " DNS 36 | read -p "Enter IP address: " IP 37 | 38 | SAN="DNS:localhost" 39 | if [ ! -z "$DNS" ]; then 40 | SAN="$SAN,DNS:$DNS" 41 | fi 42 | 43 | if [ ! -z "$IP" ]; then 44 | SAN="$SAN,IP:$IP" 45 | fi 46 | 47 | mkdir -p tls 48 | [ -f tls/ca.key ] || openssl genrsa -out tls/ca.key 4096 49 | openssl req \ 50 | -x509 -new -nodes -sha256 \ 51 | -key tls/ca.key \ 52 | -days 3650 \ 53 | -subj '/O=Atophttpd Test/CN=Certificate Authority' \ 54 | -out tls/ca.crt 55 | 56 | cat > tls/openssl.cnf <<_END_ 57 | [ server_cert ] 58 | keyUsage = digitalSignature, keyEncipherment 59 | nsCertType = server 60 | subjectAltName = ${SAN} 61 | 62 | [ client_cert ] 63 | keyUsage = digitalSignature, keyEncipherment 64 | nsCertType = client 65 | _END_ 66 | 67 | generate_cert server "Server-only" "-extfile tls/openssl.cnf -extensions server_cert" 68 | generate_cert client "Client-only" "-extfile tls/openssl.cnf -extensions client_cert" 69 | generate_cert atophttpd "Generic-cert" 70 | 71 | [ -f tls/atophttpd.dh ] || openssl dhparam -out tls/atophttpd.dh 2048 72 | -------------------------------------------------------------------------------- /http/template/html/disk.html: -------------------------------------------------------------------------------- 1 | 26 |
27 |
PID
28 |
TID
29 |
RDDSK
30 |
WRDSK
31 |
WCANCL
32 |
DSK
33 |
CMD
34 |
35 | 36 |
37 |
38 |
39 |
40 |
{pid}
41 |
{tid}
42 |
{rsz}
43 |
{wsz}
44 |
{cwsz}
45 |
{diskbusy}
46 |
{name}
47 |
48 |
49 |
50 |
51 | -------------------------------------------------------------------------------- /http/template/html/container.html: -------------------------------------------------------------------------------- 1 | 26 |
27 |
NPROCS
28 |
SYSCPU
29 |
USRCPU
30 |
VSIZE
31 |
RSIZE
32 |
PSIZE
33 |
LOCKSZ
34 |
SWAPSZ
35 |
RDDSK
36 |
WRDSK
37 |
RNET
38 |
SNET
39 |
DSK
40 |
CID
41 |
COMMAND-LINE
42 |
43 | 44 |
45 |
46 |
47 |
48 |
{pid}
49 |
{tid}
50 |
{state}
51 |
{diskbusy}
52 |
{cmdline}
53 |
54 |
55 |
56 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ATOPHTTPD 2 | 3 | ## Introduction of [atop](https://www.atoptool.nl) 4 | 5 | 6 | Atop is an ASCII full-screen performance monitor for Linux that is capable 7 | of reporting the activity of all processes (even if processes have finished 8 | during the interval), daily logging of system and process activity for 9 | long-term analysis, highlighting overloaded system resources by using colors, 10 | etcetera. At regular intervals, it shows system-level activity related to the 11 | CPU, memory, swap, disks (including LVM) and network layers, and for every 12 | process (and thread) it shows e.g. the CPU utilization, memory growth, 13 | disk utilization, priority, username, state, and exit code. 14 | In combination with the optional kernel module *netatop*, 15 | it even shows network activity per process/thread. 16 | In combination with the optional daemon *atopgpud*, 17 | it also shows GPU activity on system level and process level. 18 | 19 | ## Introduction of [atophttpd](https://github.com/pizhenwei/atophttpd) 20 | atop records the system level and process level information into log files 21 | as a daemon process, then an end user accesses the server, runs command 22 | `atop -r /var/log/atop/atop_20230105 -b 12:34` to analyze the performance, 23 | system status, process status at a specified time stamp. 24 | 25 | atophttpd runs as a daemon, reads the atop log files, and provide a HTTP 1.1 26 | service. This allows to use atop by a web browser without server login, it's 27 | also possible to query the system level and process level information in 28 | batch. 29 | 30 | ## HOWTO 31 | ### run atop daemon 32 | * By systemd: `systemctl status atop.service`(test atop service) and `systemctl start atop.service`(start atop service). 33 | * By command: `atop -w /var/log/atop/atop_20230105 10`. 34 | 35 | ### run atophttpd daemon: 36 | ``` 37 | make 38 | ./atophttpd -d #run in daemon only on localhost 39 | ./atophttpd -d -a ${IP} #run in daemon on ip 40 | ``` 41 | 42 | ### access atophttpd server: 43 | * By a web browser, for example: `192.168.1.100:2867`, 44 | to get the help page by `192.168.1.100:2867/help`. 45 | 46 | * By curl command: `curl 'http://127.0.0.1:2867/showsamp?lables=ALL×tamp=1675158274&encoding=none' | jq `. 47 | 48 | ### Generate TLS certification: 49 | ``` 50 | bash gen-cert.sh 51 | ``` 52 | * CertFile will be generated under `tls/` 53 | 54 | ### run atophttpd daemon with TLS: 55 | ``` 56 | make USE_TLS=YES 57 | /atophttpd -t 2868 -C tls/ca.crt -c tls/server.crt -k tls/server.key 58 | ``` 59 | 60 | Or use default TLS config: 61 | 62 | * CA cert file default use `/etc/pki/CA/ca.crt` 63 | * Server cert file default use `/etc/pki/atophttpd/server.crt` 64 | * Server key file default use `/etc/pki/atophttpd/server.key` 65 | 66 | ### access atophttpd server with TLS: 67 | * By curl command: 68 | ``` 69 | curl --cacert tls/ca.crt --cert tls/client.crt --key tls/client.key 'https://127.0.0.1:2868/showsamp?lables=ALL×tamp=1684402523&encoding=none' 70 | ``` 71 | 72 | ## Limitation 73 | Currently, atophttpd supports atop v2.8 only. 74 | -------------------------------------------------------------------------------- /connection.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 enhua zhou 3 | * 4 | * Authors: 5 | * enhua zhou 6 | * 7 | * Design and some codes are taken from redis. 8 | * 9 | * This work is licensed under the terms of the GNU GPL, version 2 or later. 10 | * See the COPYING file in the top-level directory. 11 | */ 12 | 13 | #ifndef _CONNECTION_H_ 14 | #define _CONNECTION_H_ 15 | 16 | #define CONN_TYPE_SOCKET "tcp" 17 | #define CONN_TYPE_TLS "tls" 18 | #define CONN_TYPE_MAX 8 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | typedef struct connection connection; 26 | 27 | typedef struct connection_type { 28 | const char* (*get_type)(struct connection* conn); 29 | 30 | void (*init)(void); 31 | void (*cleanup)(void); 32 | int (*configure)(void* priv, int reconfiguration); 33 | 34 | connection* (*conn_create)(int port, char *addr); 35 | int (*listen)(connection* listener); 36 | 37 | int (*accept)(struct connection* listener, struct connection* conn); 38 | void (*close)(struct connection* conn); 39 | void (*shutdown)(struct connection* conn); 40 | 41 | int (*write)(struct connection* conn, const void* data, size_t data_len); 42 | int (*writev)(struct connection* conn, const struct iovec* iov, int iovcnt); 43 | int (*read)(struct connection* conn, void* buf, size_t buf_len); 44 | } connection_type; 45 | 46 | struct connection { 47 | connection_type* type; 48 | int fd; 49 | int port; 50 | int is_local; 51 | char *bindaddr; 52 | }; 53 | 54 | static inline int conn_configure(connection_type* ct, void* priv, int reconfigure) { 55 | return ct->configure(priv, reconfigure); 56 | } 57 | 58 | static inline connection* conn_create(connection_type* ct, int port, char *addr) { 59 | return ct->conn_create(port, addr); 60 | } 61 | 62 | static inline int conn_listen(connection* listener) { 63 | return listener->type->listen(listener); 64 | } 65 | 66 | static inline int conn_accept(connection* listener, connection* conn) { 67 | return conn->type->accept(listener, conn); 68 | } 69 | 70 | static inline void conn_close(connection* conn) { 71 | return conn->type->close(conn); 72 | } 73 | 74 | static inline void conn_shutdown(connection* conn) { 75 | return conn->type->shutdown(conn); 76 | } 77 | 78 | static inline int conn_write(connection* conn, const void* data, size_t data_len) { 79 | return conn->type->write(conn, data, data_len); 80 | } 81 | 82 | static inline int conn_writev(connection* conn, const struct iovec* iovs, int iovcnt) { 83 | return conn->type->writev(conn, iovs, iovcnt); 84 | } 85 | 86 | static inline int conn_read(connection* conn, void* buf, size_t buf_len) { 87 | return conn->type->read(conn, buf, buf_len); 88 | } 89 | 90 | void conntype_initialize(void); 91 | int conntype_register(connection_type* ct); 92 | 93 | int register_conntype_socket(); 94 | int register_conntype_tls(); 95 | 96 | int listen_to_port(int port, char* bindaddr, int af); 97 | connection_type* get_conntype_by_name(const char* typename); 98 | int get_conntype_index_by_name(const char* typename); 99 | 100 | #endif /* _CONNECTION_H_ */ 101 | -------------------------------------------------------------------------------- /http/template/html/generic.html: -------------------------------------------------------------------------------- 1 | 26 | 27 |
28 |
PID
29 |
SYSCPU
30 |
USRCPU
31 |
RDELAY
32 |
BDELAY
33 |
VGROW
34 |
RGROW
35 |
RDDSK
36 |
WRDSK
37 |
RUID
38 |
EUID
39 |
ST
40 |
EXC
41 |
THR
42 |
S
43 |
CPUNR
44 |
CPU
45 |
CMD
46 |
47 | 48 |
49 |
50 |
51 |
52 |
{pid}
53 |
{stime_unit_time}
54 |
{utime_unit_time}
55 |
{rundelay}
56 |
{blkdelay}
57 |
{vgrow}
58 |
{rgrow}
59 |
{rsz}
60 |
{wsz}
61 |
{ruid}
62 |
{euid}
63 |
{st}
64 |
{exitcode}
65 |
{nthr}
66 |
{state}
67 |
{curcpu}
68 |
{cpubusy}
69 |
{name}
70 |
71 |
72 |
73 |
-------------------------------------------------------------------------------- /socket.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 enhua zhou 3 | * 4 | * Authors: 5 | * enhua zhou 6 | * 7 | * Design and some codes are taken from redis. 8 | * 9 | * This work is licensed under the terms of the GNU GPL, version 2 or later. 10 | * See the COPYING file in the top-level directory. 11 | */ 12 | #include "httpd.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | static connection_type CT_Socket; 20 | 21 | static const char* conn_socket_get_type(connection* conn) { 22 | return CONN_TYPE_SOCKET; 23 | } 24 | 25 | static connection* conn_socket_create(int port, char *addr) { 26 | connection* conn = calloc(1, sizeof(connection)); 27 | conn->type = &CT_Socket; 28 | conn->fd = -1; 29 | conn->port = port; 30 | conn->bindaddr = addr; 31 | 32 | return conn; 33 | } 34 | 35 | static int conn_socket_listen(connection* listener) { 36 | int listenfd = -1; 37 | 38 | if (listener->port == -1) { 39 | return -EINVAL; 40 | } 41 | 42 | if (strchr(listener->bindaddr, ':')) { 43 | listenfd = listen_to_port(listener->port, listener->bindaddr, AF_INET6); 44 | } else { 45 | listenfd = listen_to_port(listener->port, listener->bindaddr, AF_INET); 46 | } 47 | 48 | if (listenfd == -1) { 49 | return -EINVAL; 50 | } 51 | 52 | listener->fd = listenfd; 53 | // printf("socket listen on port %d, addr %s\n", listener->port, listener->bindaddr); 54 | return 0; 55 | } 56 | 57 | static int conn_socket_accept(connection* listener, connection* conn) { 58 | struct sockaddr_in cliaddr; 59 | socklen_t addrlen = sizeof(cliaddr); 60 | 61 | int clifd = accept(listener->fd, (struct sockaddr*)&cliaddr, &addrlen); 62 | if (clifd < 0) 63 | return -errno; 64 | 65 | conn->fd = clifd; 66 | return 0; 67 | } 68 | 69 | static void conn_socket_shutdown(connection* conn) { 70 | if (conn->fd == -1) 71 | return; 72 | 73 | shutdown(conn->fd, SHUT_RDWR); 74 | } 75 | 76 | static void conn_socket_close(connection* conn) { 77 | if (conn->fd == -1) 78 | return; 79 | 80 | close(conn->fd); 81 | conn->fd = -1; 82 | } 83 | 84 | static int conn_socket_write(struct connection* conn, const void* data, size_t data_len) { 85 | if (conn->fd == -1) 86 | return -EINVAL; 87 | 88 | return write(conn->fd, data, data_len); 89 | } 90 | 91 | static int conn_socket_writev(struct connection* conn, const struct iovec* iov, int iovcnt) { 92 | if (conn->fd == -1) 93 | return -EINVAL; 94 | 95 | return writev(conn->fd, iov, iovcnt); 96 | } 97 | 98 | static int conn_socket_read(struct connection* conn, void* buf, size_t buf_len) { 99 | if (conn->fd == -1) 100 | return -EINVAL; 101 | 102 | int ret = read(conn->fd, buf, buf_len); 103 | if (ret <= 0) { 104 | if (errno != EAGAIN) { 105 | return -errno; 106 | } 107 | return -EAGAIN; 108 | } 109 | 110 | return ret; 111 | } 112 | 113 | static connection_type CT_Socket = { 114 | .get_type = conn_socket_get_type, 115 | 116 | .init = NULL, 117 | .configure = NULL, 118 | .cleanup = NULL, 119 | 120 | .conn_create = conn_socket_create, 121 | 122 | .listen = conn_socket_listen, 123 | .accept = conn_socket_accept, 124 | .shutdown = conn_socket_shutdown, 125 | .close = conn_socket_close, 126 | 127 | .write = conn_socket_write, 128 | .writev = conn_socket_writev, 129 | .read = conn_socket_read}; 130 | 131 | int register_conntype_socket() { 132 | return conntype_register(&CT_Socket); 133 | } 134 | -------------------------------------------------------------------------------- /http/template/html/memory.html: -------------------------------------------------------------------------------- 1 | 26 |
27 |
PID
28 |
TID
29 |
MINFLT
30 |
MAJFLT
31 |
VSTEXT
32 |
VSLIBS
33 |
VDATA
34 |
VSTACK
35 |
LOCKSZ
36 |
VSIZE
37 |
RSIZE
38 |
PSIZE
39 |
VGROW
40 |
RGROW
41 |
SWAPSZ
42 |
RUID
43 |
EUID
44 |
MEM
45 |
CMD
46 |
47 | 48 |
49 |
50 |
51 |
52 |
{pid}
53 |
{tid}
54 |
{minflt}
55 |
{majflt}
56 |
{vexec}
57 |
{vlibs}
58 |
{vdata}
59 |
{vstack}
60 |
{vlock}
61 |
{vmem}
62 |
{rmem}
63 |
{pmem}
64 |
{vgrow}
65 |
{rgrow}
66 |
{vswap}
67 |
{ruid}
68 |
{euid}
69 |
{membusy}
70 |
{name}
71 |
72 |
73 |
74 |
75 | -------------------------------------------------------------------------------- /connection.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 enhua zhou 3 | * 4 | * Authors: 5 | * enhua zhou 6 | * 7 | * Design and some codes are taken from redis. 8 | * 9 | * This work is licensed under the terms of the GNU GPL, version 2 or later. 10 | * See the COPYING file in the top-level directory. 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "assert.h" 21 | #include "connection.h" 22 | 23 | static connection_type* conn_types[CONN_TYPE_MAX]; 24 | 25 | void conntype_initialize(void) { 26 | register_conntype_socket(); 27 | register_conntype_tls(); 28 | } 29 | 30 | int conntype_register(connection_type* ct) { 31 | const char* typename = ct->get_type(NULL); 32 | connection_type* tmpct; 33 | int type; 34 | 35 | /* find an empty slot to store the new connection type */ 36 | for (type = 0; type < CONN_TYPE_MAX; type++) { 37 | tmpct = conn_types[type]; 38 | if (!tmpct) 39 | break; 40 | 41 | /* ignore case, we really don't care "tls"/"TLS" */ 42 | if (!strcasecmp(typename, tmpct->get_type(NULL))) { 43 | printf("Connection types %s already registered\n", typename); 44 | return -errno; 45 | } 46 | } 47 | 48 | if (type == CONN_TYPE_MAX) 49 | return -ENOMEM; 50 | 51 | conn_types[type] = ct; 52 | 53 | if (ct->init) { 54 | ct->init(); 55 | } 56 | 57 | printf("Connection type %s registered\n", typename); 58 | return -errno; 59 | } 60 | 61 | connection_type* get_conntype_by_name(const char* typename) { 62 | connection_type* ct; 63 | 64 | for (int type = 0; type < CONN_TYPE_MAX; type++) { 65 | ct = conn_types[type]; 66 | if (!ct) 67 | break; 68 | 69 | if (!strcasecmp(typename, ct->get_type(NULL))) 70 | return ct; 71 | } 72 | 73 | printf("Missing implement of connection type %s\n", typename); 74 | 75 | return NULL; 76 | } 77 | 78 | int get_conntype_index_by_name(const char* typename) { 79 | connection_type* ct; 80 | for (int type = 0; type < CONN_TYPE_MAX; type++) { 81 | ct = conn_types[type]; 82 | if (!ct) 83 | break; 84 | 85 | if (!strcasecmp(typename, ct->get_type(NULL))) 86 | return type; 87 | } 88 | 89 | printf("Missing implement of connection type %s. Please re-compile atophttpd.\n", typename); 90 | 91 | return -1; 92 | } 93 | 94 | int listen_to_port(int port, char* bindaddr, int af) { 95 | struct addrinfo hints; 96 | struct addrinfo *res, *p; 97 | int ret, sockfd = -1; 98 | char _port[6]; 99 | static const int reuse = 1; 100 | 101 | snprintf(_port, 6, "%d", port); 102 | memset(&hints, 0, sizeof(hints)); 103 | hints.ai_family = af; 104 | hints.ai_socktype = SOCK_STREAM; 105 | hints.ai_flags = AI_PASSIVE; 106 | 107 | if (bindaddr && !strcmp("*", bindaddr)) 108 | bindaddr = NULL; 109 | if (af == AF_INET6 && bindaddr && !strcmp("::*", bindaddr)) 110 | bindaddr = NULL; 111 | 112 | ret = getaddrinfo(bindaddr, _port, &hints, &res); 113 | if (ret != 0) { 114 | perror("failed get addr info: "); 115 | goto error; 116 | } 117 | 118 | for (p = res; p != NULL; p = p->ai_next) { 119 | sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); 120 | if (sockfd == -1) { 121 | perror("failed get socket fd: "); 122 | continue; 123 | } 124 | 125 | ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)); 126 | if (ret < 0) { 127 | perror("socket set REUSEADDR failed: "); 128 | goto error; 129 | } 130 | ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)); 131 | if (ret < 0) { 132 | perror("socket set REUSEPORT failed: "); 133 | goto error; 134 | } 135 | 136 | ret = bind(sockfd, p->ai_addr, p->ai_addrlen); 137 | if (ret == -1) { 138 | perror("socket bind failed: "); 139 | close(sockfd); 140 | sockfd = -1; 141 | continue; 142 | } 143 | 144 | ret = listen(sockfd, 128); 145 | if (ret == -1) { 146 | perror("socket listen failed: "); 147 | close(sockfd); 148 | sockfd = -1; 149 | continue; 150 | } 151 | 152 | goto end; 153 | } 154 | 155 | error: 156 | if (sockfd != -1) { 157 | close(sockfd); 158 | sockfd = -1; 159 | } 160 | 161 | end: 162 | freeaddrinfo(res); 163 | return sockfd; 164 | } 165 | -------------------------------------------------------------------------------- /cache.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 zhenwei pi 3 | * 4 | * Authors: 5 | * zhenwei pi 6 | * 7 | * This work is licensed under the terms of the GNU GPL, version 2 or later. 8 | * See the COPYING file in the top-level directory. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "cache.h" 17 | 18 | #define RECORDS_TRUNK (60 * 60 / 10) 19 | 20 | static int nr_caches; 21 | static struct cache_t **caches; 22 | 23 | struct cache_t *cache_find(const char *name) 24 | { 25 | for (int i = 0; i < nr_caches; i++) { 26 | struct cache_t *cache = caches[i]; 27 | assert(cache); 28 | if (!strcmp(cache->name, name)) 29 | return cache; 30 | } 31 | 32 | return NULL; 33 | } 34 | 35 | struct cache_t *cache_alloc(const char *name) 36 | { 37 | struct cache_t *cache = cache_find(name); 38 | 39 | assert(!cache); 40 | 41 | cache = calloc(1, sizeof(*cache)); 42 | assert(cache); 43 | 44 | cache->name = strdup(name); 45 | assert(cache->name); 46 | 47 | cache->nr_elems = 0; 48 | cache->max_elems = RECORDS_TRUNK; 49 | cache->elems = calloc(cache->max_elems, sizeof(struct cache_elem_t)); 50 | assert(cache->elems); 51 | 52 | if (!caches) 53 | caches = calloc(1, sizeof(struct cache_t *)); 54 | else 55 | caches = realloc(caches, sizeof(struct cache_t *) * (nr_caches + 1)); 56 | 57 | caches[nr_caches++] = cache; 58 | 59 | return cache; 60 | } 61 | 62 | void cache_free(const char *name) 63 | { 64 | struct cache_t *cache = cache_find(name); 65 | 66 | if (!cache) 67 | return; 68 | 69 | free(cache->elems); 70 | free(cache->name); 71 | free(cache); 72 | 73 | for (int i = 0; i < nr_caches; i++) { 74 | if (caches[i] == cache) { 75 | memmove(&caches[i], &caches[i + 1], (nr_caches - i - 1) * sizeof(caches[0])); 76 | break; 77 | } 78 | } 79 | 80 | nr_caches--; 81 | caches = realloc(caches, sizeof(struct cache_t *) * (nr_caches)); 82 | } 83 | 84 | static struct cache_t *__cache_get(time_t time) 85 | { 86 | for (int i = 0; i < nr_caches; i++) { 87 | struct cache_t *cache = caches[i]; 88 | struct cache_elem_t *first_elem, *last_elem; 89 | 90 | assert(cache->nr_elems); 91 | first_elem = &cache->elems[0]; 92 | last_elem = &cache->elems[cache->nr_elems - 1]; 93 | if ((time >= first_elem->time) && (time <= last_elem->time)) 94 | return cache; 95 | } 96 | 97 | return NULL; 98 | } 99 | 100 | struct cache_t *cache_get(time_t time, off_t *off) 101 | { 102 | struct cache_t *cache = __cache_get(time); 103 | 104 | if (!cache) 105 | return NULL; 106 | 107 | if (cache->nr_elems <= 2) { 108 | *off = cache->elems[0].off; 109 | return cache; 110 | } 111 | 112 | int left = 0; 113 | int right = cache->nr_elems; 114 | 115 | while (left < right) { 116 | int mid = (left + right) >> 1; 117 | struct cache_elem_t *elem = &cache->elems[mid]; 118 | if (time == elem->time) { 119 | *off = elem->off; 120 | return cache; 121 | } 122 | 123 | if (time < elem->time) { 124 | right = mid; 125 | } else { 126 | left = mid + 1; 127 | } 128 | } 129 | 130 | struct cache_elem_t *elem = &cache->elems[left]; 131 | *off = elem->off; 132 | return cache; 133 | } 134 | 135 | static int cache_elems_cmp(const void *p1, const void *p2) 136 | { 137 | struct cache_elem_t *e1 = (struct cache_elem_t *)p1; 138 | struct cache_elem_t *e2 = (struct cache_elem_t *)p2; 139 | 140 | return e1->time > e2->time; 141 | } 142 | 143 | void cache_set(struct cache_t *cache, time_t time, off_t off) 144 | { 145 | if (cache->nr_elems == cache->max_elems) { 146 | cache->max_elems += RECORDS_TRUNK; 147 | cache->elems = realloc(cache->elems, sizeof(struct cache_elem_t) * cache->max_elems); 148 | } 149 | 150 | struct cache_elem_t *new_elem = &cache->elems[cache->nr_elems++]; 151 | new_elem->time = time; 152 | new_elem->off = off; 153 | 154 | if (cache->nr_elems == 1) 155 | return; 156 | 157 | struct cache_elem_t *last_elem = &cache->elems[cache->nr_elems - 2]; 158 | if (last_elem->time > new_elem->time) { 159 | qsort(cache->elems, cache->nr_elems, sizeof(struct cache_elem_t), cache_elems_cmp); 160 | } 161 | } 162 | 163 | static int cache_cmp(const void *p1, const void *p2) 164 | { 165 | struct cache_t **c1 = (struct cache_t **)p1; 166 | struct cache_t **c2 = (struct cache_t **)p2; 167 | 168 | return (*c1)->elems[0].time > (*c2)->elems[0].time; 169 | } 170 | 171 | void cache_sort() 172 | { 173 | qsort(caches, nr_caches, sizeof(struct cache_t *), cache_cmp); 174 | } 175 | 176 | struct cache_t *cache_get_recent() 177 | { 178 | if (!nr_caches) 179 | return NULL; 180 | 181 | return caches[nr_caches - 1]; 182 | } 183 | 184 | #ifdef CACHE_TEST 185 | int main() 186 | { 187 | assert(!cache_find("test0")); 188 | 189 | struct cache_t *cache0 = cache_alloc("test0"); 190 | cache_set(cache0, 100, 1000); 191 | assert(cache0->elems[0].time == 100); 192 | cache_set(cache0, 102, 1002); 193 | assert(cache0->elems[0].time == 100); 194 | assert(cache0->elems[1].time == 102); 195 | cache_set(cache0, 101, 1001); 196 | assert(cache0->nr_elems == 3); 197 | assert(cache0->elems[0].time < cache0->elems[1].time); 198 | assert(cache0->elems[1].time < cache0->elems[2].time); 199 | 200 | struct cache_t *cache1 = cache_alloc("test1"); 201 | cache_set(cache1, 200, 2000); 202 | cache_free("test0"); 203 | assert(nr_caches == 1); 204 | assert(caches[0]->elems[0].time == 200); 205 | assert(caches[0]->elems[0].off == 2000); 206 | 207 | return 0; 208 | } 209 | #endif 210 | -------------------------------------------------------------------------------- /tls.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 enhua zhou 3 | * 4 | * Authors: 5 | * enhua zhou 6 | * 7 | * Design and some codes are taken from redis. 8 | * 9 | * This work is licensed under the terms of the GNU GPL, version 2 or later. 10 | * See the COPYING file in the top-level directory. 11 | */ 12 | 13 | #include "httpd.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #ifdef USE_TLS 22 | typedef struct tls_connection { 23 | connection c; 24 | SSL* ssl; 25 | } tls_connection; 26 | 27 | SSL_CTX* tls_ctx = NULL; 28 | 29 | static connection_type CT_TLS; 30 | 31 | static const char* conn_tls_get_type(connection* conn) { 32 | return CONN_TYPE_TLS; 33 | } 34 | 35 | static void tls_init(void) { 36 | SSL_library_init(); 37 | OpenSSL_add_all_algorithms(); 38 | SSL_load_error_strings(); 39 | } 40 | 41 | static int tls_configure(void* priv, int reconfigure) { 42 | atophttpd_tls_context_config* ctx_config = (atophttpd_tls_context_config*)priv; 43 | SSL_CTX* ctx = NULL; 44 | 45 | if (!ctx_config->cert_file) { 46 | perror("No tls-cert-file configured!"); 47 | goto error; 48 | } 49 | 50 | if (!ctx_config->key_file) { 51 | perror("No tls-key-file configured!"); 52 | goto error; 53 | } 54 | 55 | if (!ctx_config->ca_cert_file) { 56 | perror("No tls-ca-cert-file configured!"); 57 | goto error; 58 | } 59 | 60 | ctx = SSL_CTX_new(TLS_method()); 61 | if (!ctx) { 62 | perror("Create SSL context Error"); 63 | goto error; 64 | } 65 | 66 | if (SSL_CTX_use_certificate_file(ctx, ctx_config->cert_file, SSL_FILETYPE_PEM) <= 0) { 67 | perror("ERROR loading server certificate"); 68 | goto error; 69 | } 70 | 71 | if (SSL_CTX_use_PrivateKey_file(ctx, ctx_config->key_file, SSL_FILETYPE_PEM) <= 0) { 72 | perror("ERROR loading server private key"); 73 | goto error; 74 | } 75 | 76 | if (!SSL_CTX_check_private_key(ctx)) { 77 | perror("Private key does not match certificate\n"); 78 | goto error; 79 | } 80 | 81 | if (SSL_CTX_load_verify_locations(ctx, ctx_config->ca_cert_file, NULL) <= 0) { 82 | perror("ERROR loading ca certificate"); 83 | goto error; 84 | } 85 | 86 | SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); 87 | 88 | SSL_CTX_free(tls_ctx); 89 | tls_ctx = ctx; 90 | return 0; 91 | 92 | error: 93 | if (ctx) 94 | SSL_CTX_free(ctx); 95 | return -errno; 96 | } 97 | 98 | static void tls_cleanup(void) { 99 | if (tls_ctx) { 100 | SSL_CTX_free(tls_ctx); 101 | tls_ctx = NULL; 102 | } 103 | } 104 | 105 | static connection* conn_tls_create(int port, char *addr) { 106 | tls_connection* conn = calloc(1, sizeof(tls_connection)); 107 | conn->c.type = &CT_TLS; 108 | conn->c.fd = -1; 109 | conn->c.port = port; 110 | conn->c.bindaddr = addr; 111 | 112 | return (connection*)conn; 113 | } 114 | 115 | static int conn_tls_listen(connection* listener) { 116 | // printf("tls listen on port %d, addr %s\n", listener->port, listener->bindaddr); 117 | return get_conntype_by_name(CONN_TYPE_SOCKET)->listen(listener); 118 | } 119 | 120 | 121 | static int conn_tls_accept(connection * listener, connection* conn) { 122 | int ret = get_conntype_by_name(CONN_TYPE_SOCKET)->accept(listener, conn); 123 | if (ret < 0) { 124 | perror("tls conn accept failed at tcp accept."); 125 | return -errno; 126 | } 127 | 128 | tls_connection* tls_conn = (tls_connection*)conn; 129 | SSL_CTX* ctx = tls_ctx; 130 | 131 | if (!ctx) { 132 | perror("tls conn create but ctx is null"); 133 | return -EINVAL; 134 | } 135 | tls_conn->ssl = SSL_new(ctx); 136 | 137 | if (tls_conn->ssl == NULL) { 138 | perror("accept ssl is null!"); 139 | return -EINVAL; 140 | } 141 | 142 | if (tls_conn->c.fd == -1) { 143 | perror("tls clifd is illegal!"); 144 | return -EINVAL; 145 | } 146 | 147 | SSL_set_fd(tls_conn->ssl, tls_conn->c.fd); 148 | if (SSL_accept(tls_conn->ssl) <= 0) { 149 | printf("SSL_TLS handshake failed: %s\n", ERR_error_string(ERR_get_error(), NULL)); 150 | return -errno; 151 | } 152 | 153 | return 0; 154 | } 155 | 156 | static void conn_shutdown_tls(connection* conn) { 157 | tls_connection* tls_conn = (tls_connection*)conn; 158 | 159 | if (tls_conn->ssl) { 160 | SSL_shutdown(tls_conn->ssl); 161 | SSL_free(tls_conn->ssl); 162 | 163 | tls_conn->ssl = NULL; 164 | } 165 | 166 | get_conntype_by_name(CONN_TYPE_SOCKET)->shutdown(conn); 167 | return; 168 | } 169 | 170 | static void conn_close_tls(connection* conn) { 171 | tls_connection* tls_conn = (tls_connection*)conn; 172 | 173 | if (tls_conn->ssl) { 174 | SSL_shutdown(tls_conn->ssl); 175 | SSL_free(tls_conn->ssl); 176 | 177 | tls_conn->ssl = NULL; 178 | } 179 | 180 | get_conntype_by_name(CONN_TYPE_SOCKET)->close(conn); 181 | return; 182 | } 183 | 184 | static int conn_write_tls(connection* conn, const void* data, size_t data_len) { 185 | tls_connection* tls_conn = (tls_connection*)conn; 186 | if (tls_conn->ssl == NULL) { 187 | return -EINVAL; 188 | } 189 | 190 | int ret = SSL_write(tls_conn->ssl, data, data_len); 191 | if (ret < 0) { 192 | perror("SSL write error"); 193 | return -errno; 194 | } 195 | 196 | return 0; 197 | } 198 | 199 | static int conn_writev_tls(connection* conn, const struct iovec* iov, int iovcnt) { 200 | tls_connection* tls_conn = (tls_connection*)conn; 201 | 202 | if (tls_conn->ssl == NULL) { 203 | return -EINVAL; 204 | } 205 | 206 | size_t iov_bytes_len = 0; 207 | for (int i = 0; i < 3; i++) { 208 | iov_bytes_len += iov[i].iov_len; 209 | } 210 | 211 | char *ssl_buf; 212 | ssl_buf = malloc(iov_bytes_len); 213 | if (!ssl_buf) { 214 | perror("SSL have no mem"); 215 | return -ENOMEM; 216 | } 217 | size_t offset = 0; 218 | for (int i = 0; i < iovcnt; i++) { 219 | memcpy(ssl_buf + offset, iov[i].iov_base, iov[i].iov_len); 220 | offset += iov[i].iov_len; 221 | } 222 | 223 | int ret = SSL_write(tls_conn->ssl, ssl_buf, iov_bytes_len); 224 | if (ret < 0) { 225 | perror("SSL writev error"); 226 | ret = -errno; 227 | } 228 | 229 | free(ssl_buf); 230 | return ret; 231 | } 232 | 233 | static int conn_read_tls(connection* conn, void* buf, size_t buf_len) { 234 | tls_connection* tls_conn = (tls_connection*)conn; 235 | 236 | if (tls_conn->ssl == NULL) { 237 | return -EINVAL; 238 | } 239 | 240 | int ret = SSL_read(tls_conn->ssl, buf, buf_len); 241 | if (ret <= 0) { 242 | if (errno != EAGAIN) { 243 | return -errno; 244 | } 245 | 246 | return -EAGAIN; 247 | } 248 | return ret; 249 | } 250 | 251 | static connection_type CT_TLS = { 252 | .get_type = conn_tls_get_type, 253 | 254 | .init = tls_init, 255 | .configure = tls_configure, 256 | .cleanup = tls_cleanup, 257 | 258 | .conn_create = conn_tls_create, 259 | 260 | .listen = conn_tls_listen, 261 | 262 | .accept = conn_tls_accept, 263 | .shutdown = conn_shutdown_tls, 264 | .close = conn_close_tls, 265 | 266 | .write = conn_write_tls, 267 | .writev = conn_writev_tls, 268 | .read = conn_read_tls}; 269 | 270 | int register_conntype_tls() { 271 | return conntype_register(&CT_TLS); 272 | } 273 | 274 | #else 275 | 276 | int register_conntype_tls() { 277 | printf("ConnectionType %s not builtin\n", CONN_TYPE_TLS); 278 | return -EINVAL; 279 | } 280 | 281 | #endif 282 | -------------------------------------------------------------------------------- /rawlog.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 zhenwei pi 3 | * 4 | * Authors: 5 | * zhenwei pi 6 | * 7 | * This work is licensed under the terms of the GNU GPL, version 2 or later. 8 | * See the COPYING file in the top-level directory. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "config.h" 27 | 28 | #include "atop.h" 29 | #include "cache.h" 30 | #include "httpd.h" 31 | #include "json.h" 32 | #include "rawlog.h" 33 | 34 | /* a little tricky: implemented in atop/version.c */ 35 | unsigned short getnumvers(void); 36 | 37 | static int rawlog_verify_rawheader(struct rawheader *rh) 38 | { 39 | if (rh->magic != MYMAGIC) 40 | return -EINVAL; 41 | 42 | if ((rh->rawheadlen != sizeof(struct rawheader)) || 43 | (rh->rawreclen != sizeof(struct rawrecord)) || 44 | (rh->sstatlen != sizeof(struct sstat)) || 45 | (rh->tstatlen != sizeof(struct tstat))) { 46 | return -EINVAL; 47 | } 48 | 49 | if (rh->aversion & 0x8000 && (rh->aversion & 0x7fff) != getnumvers()) 50 | return -EINVAL; 51 | 52 | return 0; 53 | } 54 | 55 | static int rawlog_rebuild_one(struct cache_t *cache) 56 | { 57 | struct cache_elem_t *elem = &cache->elems[cache->nr_elems - 1]; 58 | struct rawrecord rr; 59 | int ret = 0; 60 | 61 | int fd = open(cache->name, O_RDONLY); 62 | if (fd < 0) { 63 | printf("%s: open \"%s\" failed: %m\n", __func__, cache->name); 64 | return -errno; 65 | } 66 | 67 | off_t off = elem->off; 68 | ssize_t len = pread(fd, &rr, sizeof(rr), off); 69 | if (len < sizeof(rr)) 70 | goto close_fd; 71 | 72 | off = off + sizeof(rr) + rr.scomplen + rr.pcomplen; 73 | while (1) { 74 | len = pread(fd, &rr, sizeof(rr), off); 75 | if (len < sizeof(rr)) 76 | break; 77 | 78 | cache_set(cache, rr.curtime, off); 79 | off = off + sizeof(rr) + rr.scomplen + rr.pcomplen; 80 | } 81 | 82 | log_debug("\"%s\" has %d records of time[%ld - %ld]\n", cache->name, 83 | cache->nr_elems, cache->elems[0].time, cache->elems[cache->nr_elems - 1].time); 84 | 85 | struct stat statbuf; 86 | ret = fstat(fd, &statbuf); 87 | if (ret < 0) { 88 | printf("%s: fstat \"%s\" failed: %m\n", __func__, cache->name); 89 | ret = -errno; 90 | goto close_fd; 91 | } 92 | 93 | cache->st_size = statbuf.st_size; 94 | cache->st_mtim = statbuf.st_mtim; 95 | 96 | close_fd: 97 | close(fd); 98 | return ret; 99 | } 100 | 101 | static int rawlog_parse_one(const char *path) 102 | { 103 | struct rawheader rh; 104 | struct rawrecord rr; 105 | struct stat statbuf; 106 | struct cache_t *cache; 107 | ssize_t len; 108 | off_t off; 109 | int fd; 110 | int ret; 111 | 112 | /* if we have already build a cache, rebuild it */ 113 | cache = cache_find(path); 114 | if (cache) { 115 | ret = stat(path, &statbuf); 116 | if (ret < 0) { 117 | printf("%s: stat \"%s\" failed: %m\n", __func__, path); 118 | return -errno; 119 | } 120 | 121 | if (cache->st_size == statbuf.st_size) { 122 | return 0; 123 | } 124 | 125 | return rawlog_rebuild_one(cache); 126 | } 127 | 128 | fd = open(path, O_RDONLY); 129 | if (fd < 0) { 130 | printf("%s: open \"%s\" failed: %m\n", __func__, path); 131 | return -errno; 132 | } 133 | 134 | /* 1, read rawheader */ 135 | len = read(fd, &rh, sizeof(rh)); 136 | if (len < sizeof(rh)) { 137 | printf("%s: \"%s\" is not atop rawlog file\n", __func__, path); 138 | ret = -EINVAL; 139 | goto close_fd; 140 | } 141 | 142 | ret = rawlog_verify_rawheader(&rh); 143 | if (ret) { 144 | printf("%s: \"%s\" verify rawheader failed\n", __func__, path); 145 | goto close_fd; 146 | } 147 | 148 | /* 2, create cache for this file */ 149 | cache = cache_alloc(path); 150 | if (!cache) { 151 | ret = -ENOMEM; 152 | goto close_fd; 153 | } 154 | cache->flags = rh.supportflags; 155 | 156 | /* 3, read all rawrecords, cache time&off mapping */ 157 | off = lseek(fd, 0, SEEK_CUR); 158 | if (off < 0) { 159 | printf("%s: move offset failed\n", __func__); 160 | ret = -errno; 161 | goto close_fd; 162 | } 163 | while (1) { 164 | len = pread(fd, &rr, sizeof(rr), off); 165 | if (len < sizeof(rr)) 166 | break; 167 | 168 | cache_set(cache, rr.curtime, off); 169 | off = off + sizeof(rr) + rr.scomplen + rr.pcomplen; 170 | } 171 | 172 | /* 4, update rawlog size & st_mtim */ 173 | ret = fstat(fd, &statbuf); 174 | if (ret < 0) { 175 | printf("%s: fstat \"%s\" failed", __func__, path); 176 | ret = -errno; 177 | goto close_fd; 178 | } 179 | cache->st_size = statbuf.st_size; 180 | cache->st_mtim = statbuf.st_mtim; 181 | 182 | if (!cache->nr_elems) 183 | goto free_cache; 184 | 185 | log_debug("\"%s\" has %d records of time[%ld - %ld]\n", path, cache->nr_elems, 186 | cache->elems[0].time, cache->elems[cache->nr_elems - 1].time); 187 | ret = 0; 188 | goto close_fd; 189 | 190 | free_cache: 191 | cache_free(path); 192 | 193 | close_fd: 194 | close(fd); 195 | 196 | return ret; 197 | } 198 | 199 | int rawlog_parse_all(const char *path) 200 | { 201 | struct dirent *dirent; 202 | DIR *dir = opendir(path); 203 | char name[PATH_MAX] = {0}; 204 | 205 | if (!dir) { 206 | printf("%s: open \"%s\" failed: %m\n", __func__, path); 207 | return -errno; 208 | } 209 | 210 | while ((dirent = readdir(dir))) { 211 | if (dirent->d_type != DT_REG) 212 | continue; 213 | 214 | sprintf(name, "%s/%s", path, dirent->d_name); 215 | rawlog_parse_one(name); 216 | } 217 | 218 | cache_sort(); 219 | 220 | closedir(dir); 221 | 222 | return 0; 223 | } 224 | 225 | static int rawlog_uncompress_record(int fd, void *outbuf, unsigned long *outlen, unsigned long inlen) 226 | { 227 | Byte *inbuf; 228 | int ret = 0; 229 | 230 | inbuf = malloc(inlen); 231 | if (!inbuf) 232 | return -ENOMEM; 233 | 234 | if (read(fd, inbuf, inlen) != inlen) { 235 | ret = -EIO; 236 | goto free_buf; 237 | } 238 | 239 | if (uncompress(outbuf, outlen, inbuf, inlen) != Z_OK) 240 | ret = -ENODATA; 241 | 242 | free_buf: 243 | free(inbuf); 244 | 245 | return ret; 246 | } 247 | 248 | static int rawlog_get_sstat(int fd, struct sstat *sstat, unsigned long len) 249 | { 250 | unsigned long outlen = sizeof(struct sstat); 251 | 252 | return rawlog_uncompress_record(fd, sstat, &outlen, len); 253 | } 254 | 255 | static void rawlog_free_devtstat(struct devtstat *devtstat) 256 | { 257 | free(devtstat->taskall); 258 | free(devtstat->procall); 259 | free(devtstat->procactive); 260 | } 261 | 262 | static int rawlog_get_devtstat(int fd, struct devtstat *devtstat, struct rawrecord *rr) 263 | { 264 | unsigned long outlen = sizeof(struct tstat) * rr->ndeviat; 265 | int ret; 266 | unsigned long ntaskall = 0, nprocall = 0, nprocactive = 0, ntaskactive = 0; 267 | 268 | /* 1, allocate memory */ 269 | memset(devtstat, 0x00, sizeof(struct devtstat)); 270 | devtstat->taskall = malloc(outlen); 271 | if (!devtstat->taskall) { 272 | ret = -ENOMEM; 273 | goto free_devtstat; 274 | } 275 | 276 | devtstat->procall = malloc(sizeof(struct tstat*) * rr->totproc); 277 | if (!devtstat->procall) { 278 | ret = -ENOMEM; 279 | goto free_devtstat; 280 | } 281 | 282 | devtstat->procactive = malloc(sizeof(struct tstat*) * rr->nactproc); 283 | if (!devtstat->procactive) { 284 | ret = -ENOMEM; 285 | goto free_devtstat; 286 | } 287 | 288 | /* 2, read record and uncompress */ 289 | ret = rawlog_uncompress_record(fd, devtstat->taskall, &outlen, rr->pcomplen); 290 | if (ret) 291 | goto free_devtstat; 292 | 293 | /* 3, build devtstat */ 294 | for ( ; ntaskall < rr->ndeviat; ntaskall++) 295 | { 296 | struct tstat *tstat = devtstat->taskall + ntaskall; 297 | if (tstat->gen.isproc) 298 | { 299 | devtstat->procall[nprocall++] = tstat; 300 | if (!tstat->gen.wasinactive) 301 | devtstat->procactive[nprocactive++] = tstat; 302 | } 303 | 304 | if (!tstat->gen.wasinactive) 305 | ntaskactive++; 306 | } 307 | 308 | devtstat->ntaskall = ntaskall; 309 | devtstat->nprocall = nprocall; 310 | devtstat->nprocactive = nprocactive; 311 | devtstat->ntaskactive = ntaskactive; 312 | devtstat->totrun = rr->totrun; 313 | devtstat->totslpi = rr->totslpi; 314 | devtstat->totslpu = rr->totslpu; 315 | devtstat->totzombie = rr->totzomb; 316 | 317 | 318 | return 0; 319 | 320 | free_devtstat: 321 | rawlog_free_devtstat(devtstat); 322 | 323 | return ret; 324 | } 325 | 326 | static int rawlog_record_flags(int hflags, int rflags) 327 | { 328 | int ret = 0; 329 | 330 | if (hflags & RAWLOGNG) { 331 | if (rflags & RRACCTACTIVE) 332 | ret |= ACCTACTIVE; 333 | 334 | if (rflags & RRIOSTAT) 335 | ret |= IOSTAT; 336 | } 337 | 338 | if (rflags & RRNETATOP) 339 | ret |= NETATOP; 340 | 341 | if (rflags & RRNETATOPD) 342 | ret |= NETATOPD; 343 | 344 | if (rflags & RRCGRSTAT) 345 | ret |= CGROUPV2; 346 | 347 | if (rflags & RRDOCKSTAT) 348 | ret |= DOCKSTAT; 349 | 350 | if (rflags & RRGPUSTAT) 351 | ret |= GPUSTAT; 352 | 353 | return ret; 354 | } 355 | 356 | int rawlog_get_record(time_t ts, char *labels, connection *conn) 357 | { 358 | struct rawrecord rr; 359 | struct sstat *sstat; 360 | struct devtstat devtstat; 361 | ssize_t len; 362 | int fd; 363 | int ret = 0; 364 | int flags; 365 | off_t off; 366 | time_t recent_ts; 367 | 368 | sstat = malloc(sizeof(struct sstat)); 369 | if (sstat == NULL) { 370 | log_debug("can't alloc mem for sstat\n"); 371 | return -ENOMEM; 372 | } 373 | 374 | struct cache_t *cache = cache_get(ts, &off); 375 | if (cache) 376 | goto found; 377 | 378 | log_debug("no record @%ld\n", ts); 379 | 380 | cache = cache_get_recent(); 381 | if (!cache) { 382 | ret = -EIO; 383 | goto free_sstat; 384 | } 385 | 386 | recent_ts = cache->elems[cache->nr_elems - 1].time; 387 | if (ts < recent_ts) { 388 | ret = -EIO; 389 | goto free_sstat; 390 | } 391 | 392 | off = cache->elems[cache->nr_elems - 1].off; 393 | log_debug("use recent @%ld from %s\n", cache->elems[cache->nr_elems - 1].time, cache->name); 394 | 395 | found: 396 | log_debug("time %ld, off %ld in %s\n", ts, off, cache->name); 397 | 398 | fd = open(cache->name, O_RDONLY); 399 | if (fd < 0) { 400 | printf("%s: open \"%s\" failed: %m\n", __func__, cache->name); 401 | ret = -errno; 402 | goto free_sstat; 403 | } 404 | 405 | ret = lseek(fd, off, SEEK_CUR); 406 | if (ret < 0) { 407 | printf("%s: move offset failed\n", __func__); 408 | ret = -errno; 409 | goto close_fd; 410 | } 411 | 412 | len = read(fd, &rr, sizeof(rr)); 413 | if (len != sizeof(rr)) { 414 | printf("%s: time %ld, off %ld in %s, incomplete record\n", __func__, ts, off, cache->name); 415 | ret = -EIO; 416 | goto close_fd; 417 | } 418 | log_debug("time %ld, off %ld in %s, rr.curtime %ld\n", ts, off, cache->name, rr.curtime); 419 | 420 | ret = rawlog_get_sstat(fd, sstat, rr.scomplen); 421 | if (ret) { 422 | printf("%s: time %ld, off %ld in %s, get sstat failed\n", __func__, ts, off, cache->name); 423 | goto close_fd; 424 | } 425 | 426 | ret = rawlog_get_devtstat(fd, &devtstat, &rr); 427 | if (ret) { 428 | printf("%s: time %ld, off %ld in %s, get devtstat failed\n", __func__, ts, off, cache->name); 429 | goto close_fd; 430 | } 431 | 432 | flags = rawlog_record_flags(cache->flags, rr.flags); 433 | jsonout(flags, labels, rr.curtime, rr.interval, &devtstat, sstat, rr.nexit, rr.noverflow, 0, conn); 434 | 435 | rawlog_free_devtstat(&devtstat); 436 | 437 | close_fd: 438 | close(fd); 439 | free_sstat: 440 | free(sstat); 441 | 442 | return ret; 443 | } 444 | -------------------------------------------------------------------------------- /http/js/atop.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** This is a JavaScript for ATOP Http Server 3 | ** 4 | ** ========================================================================== 5 | ** Author: Enhua Zhou 6 | ** E-mail: zhouenhua@bytedance.com 7 | ** Date: September 2022 8 | ** -------------------------------------------------------------------------- 9 | ** Copyright (C) 2022 Enhua Zhou 10 | ** 11 | ** This program is free software; you can redistribute it and/or modify it 12 | ** under the terms of the GNU General Public License as published by the 13 | ** Free Software Foundation; either version 2, or (at your option) any 14 | ** later version. 15 | ** 16 | ** This program is distributed in the hope that it will be useful, but 17 | ** WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 19 | ** See the GNU General Public License for more details. 20 | ** 21 | ** You should have received a copy of the GNU General Public License 22 | ** along with this program; if not, write to the Free Software 23 | ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 24 | ** -------------------------------------------------------------------------- 25 | */ 26 | 27 | const OrderType = { 28 | CPU: 'CPU', 29 | DISK: 'DISK', 30 | MEM: 'MEM', 31 | } 32 | Object.freeze(OrderType); 33 | 34 | const TempleteType = { 35 | CommandLine: 'command_line', 36 | Disk: 'disk', 37 | Generic: 'generic', 38 | Memory: 'memory' 39 | } 40 | Object.freeze(TempleteType); 41 | 42 | const DEFAULT_PROC_SHOW_NUM = 200; 43 | 44 | const DEFAULT_CPU_SHOW_NUM = 0; 45 | const DEFAULT_GPU_SHOW_NUM = 2; 46 | const DEFAULT_DISK_SHOW_NUM = 1; 47 | const DEFAULT_LVM_SHOW_NUM = 4; 48 | const DEFAULT_INTERFACE_SHOW_NUM = 2; 49 | const DEFAULT_INFINIBAND_SHOW_NUM = 2; 50 | const DEFAULT_NFS_SHOW_NUM = 2; 51 | const DEFAULT_CONTAINER_SHOW_NUM = 1; 52 | const DEFAULT_NUMA_SHOW_NUM = 0; 53 | const DEFAULT_LLC_SHOW_NUM = 0; 54 | 55 | var proc_show_num = DEFAULT_PROC_SHOW_NUM; 56 | var cpu_show_num = DEFAULT_CPU_SHOW_NUM; 57 | var gpu_show_num = DEFAULT_GPU_SHOW_NUM; 58 | var lvm_show_num = DEFAULT_LVM_SHOW_NUM; 59 | var disk_show_num = DEFAULT_DISK_SHOW_NUM; 60 | var interface_show_num = DEFAULT_INTERFACE_SHOW_NUM; 61 | var infiniband_show_num = DEFAULT_INFINIBAND_SHOW_NUM; 62 | var nfs_show_num = DEFAULT_NFS_SHOW_NUM; 63 | var container_show_num = DEFAULT_CONTAINER_SHOW_NUM; 64 | var numa_show_num = DEFAULT_NUMA_SHOW_NUM; 65 | var LLC_show_num = DEFAULT_LLC_SHOW_NUM; 66 | 67 | // second 68 | var pre_timestamp = Date.parse(new Date()) / 1000; 69 | 70 | var template_type = TempleteType.Generic; 71 | var order_type = OrderType.CPU; 72 | var only_proc = true; 73 | 74 | var change_template = false 75 | var change_data = false 76 | var filter_str = "" 77 | var init = true 78 | 79 | var delta = 0 80 | const queue = [] 81 | let loading = false 82 | let p 83 | 84 | var cache_template_header = "" 85 | var cache_template_process = "" 86 | var cache_json_data = "" 87 | 88 | window.onkeydown = function (event) { 89 | var keyCode = event.key; 90 | now = Date.parse(new Date()) / 1000; 91 | 92 | switch (keyCode) { 93 | case 'b': 94 | var now_date_str = new Date(pre_timestamp * 1000).format("YYYYMMDDhhmm"); 95 | 96 | var chosed_date_str = prompt("Enter new time (format [YYYYMMDD]hhmm): ", now_date_str); 97 | if (chosed_date_str != null) { 98 | var tmp_timestamp = 0; 99 | tmp_timestamp = ParseDateToTimesample(chosed_date_str) 100 | if (tmp_timestamp != 0) { 101 | delta = tmp_timestamp - pre_timestamp 102 | } else { 103 | alert("Wrong time!") 104 | } 105 | } 106 | change_data = true 107 | break; 108 | case 'T': 109 | delta -= 10 110 | if (loading) { 111 | return 112 | } 113 | change_data = true 114 | break; 115 | case 't': 116 | delta += 10 117 | if (loading) { 118 | return 119 | } 120 | change_data = true 121 | break; 122 | case 'l': 123 | atopList(); 124 | break; 125 | case 'g': 126 | order_type = OrderType.CPU; 127 | change_template = true; 128 | template_type = TempleteType.Generic; 129 | break; 130 | case 'm': 131 | order_type = OrderType.MEM; 132 | change_template = true; 133 | template_type = TempleteType.Memory; 134 | break; 135 | case 'd': 136 | order_type = OrderType.DISK; 137 | change_template = true; 138 | template_type = TempleteType.Disk; 139 | break; 140 | case 'c': 141 | order_type = OrderType.DISK; 142 | change_template = true; 143 | template_type = TempleteType.CommandLine; 144 | break; 145 | case 'y': 146 | only_proc = !only_proc; 147 | change_template = true; 148 | break; 149 | case '/': 150 | filter_str = prompt("Command line string as regular expression (enter=no regex): ", ""); 151 | break; 152 | default: 153 | return 154 | } 155 | 156 | main(); 157 | } 158 | 159 | window.onload = function () { 160 | change_data = true 161 | change_template = true 162 | main(); 163 | } 164 | 165 | function atopList() { 166 | var new_process_show_num = prompt("Maxinum lines for process statistics (now " + proc_show_num + "): ", DEFAULT_PROC_SHOW_NUM); 167 | if (new_process_show_num != null) { 168 | proc_show_num = new_process_show_num; 169 | } 170 | 171 | var new_cpu_show_num = prompt("Maxinum lines for per-cpu statistics (now " + cpu_show_num + "): ", DEFAULT_CPU_SHOW_NUM); 172 | if (new_cpu_show_num != null) { 173 | cpu_show_num = new_cpu_show_num; 174 | } 175 | 176 | var new_gpu_show_num = prompt("Maxinum lines for per-gpu statistics (now " + gpu_show_num + "): ", DEFAULT_GPU_SHOW_NUM); 177 | if (new_gpu_show_num != null) { 178 | gpu_show_num = new_gpu_show_num; 179 | } 180 | 181 | var new_lvm_show_num = prompt("Maxinum lines for LVM statistics (now " + lvm_show_num + "): ", DEFAULT_LVM_SHOW_NUM); 182 | if (new_lvm_show_num != null) { 183 | lvm_show_num = new_lvm_show_num; 184 | } 185 | 186 | var new_disk_show_num = prompt("Maxinum lines for disk statistics (now " + disk_show_num + "): ", DEFAULT_DISK_SHOW_NUM); 187 | if (new_disk_show_num != null) { 188 | disk_show_num = new_disk_show_num; 189 | } 190 | 191 | var new_interface_show_num = prompt("Maxinum lines for interface statistics (now " + interface_show_num + "): ", DEFAULT_INTERFACE_SHOW_NUM); 192 | if (new_interface_show_num != null) { 193 | interface_show_num = new_interface_show_num; 194 | } 195 | 196 | var new_infiniband_show_num = prompt("Maxinum lines for infiniband port statistics (now " + infiniband_show_num + "): ", DEFAULT_INFINIBAND_SHOW_NUM); 197 | if (new_infiniband_show_num != null) { 198 | infiniband_show_num = new_infiniband_show_num; 199 | } 200 | 201 | var new_nfs_show_num = prompt("Maxinum lines for NFS mount statistics (now " + nfs_show_num + "): ", DEFAULT_NFS_SHOW_NUM); 202 | if (new_nfs_show_num != null) { 203 | nfs_show_num = new_nfs_show_num; 204 | } 205 | 206 | var new_container_show_num = prompt("Maxinum lines for container statistics (now " + container_show_num + "): ", DEFAULT_CONTAINER_SHOW_NUM); 207 | if (new_container_show_num != null) { 208 | container_show_num = new_container_show_num; 209 | } 210 | 211 | var new_numa_show_num = prompt("Maxinum lines for numa statistics (now " + numa_show_num + "): ", DEFAULT_NUMA_SHOW_NUM); 212 | if (new_numa_show_num != null) { 213 | numa_show_num = new_numa_show_num; 214 | } 215 | 216 | var new_LLC_show_num = prompt("Maxinum lines for LLC statistics (now " + LLC_show_num + "): ", DEFAULT_LLC_SHOW_NUM); 217 | if (new_LLC_show_num != null) { 218 | LLC_show_num = new_LLC_show_num; 219 | } 220 | } 221 | 222 | function main() { 223 | var req_timestamp = pre_timestamp + delta 224 | var req_template_type = template_type 225 | var req_change_data = change_data 226 | var req_change_template = change_template 227 | if (delta == 0 && init == true) { 228 | getHtmlTemplateProcessFromServer(req_timestamp, req_template_type, req_change_data, req_change_template).then(getHtmlTemplateHeaderFromServer).then(getJsonRawDataFromServer).then(res => { 229 | LoadHtmlFromJson(cache_json_data, constructTemplate(cache_template_header, cache_template_process)) 230 | while (p = queue.shift()) { 231 | p.resolve() 232 | } 233 | init = false 234 | loading = false 235 | change_data = false 236 | change_template = false 237 | }) 238 | return 239 | } else { 240 | delta = 0 241 | getHtmlTemplateProcessFromServer(req_timestamp, req_template_type, req_change_data, req_change_template).then(getJsonRawDataFromServer).then(res => { 242 | LoadHtmlFromJson(cache_json_data, constructTemplate(cache_template_header, cache_template_process)) 243 | while (p = queue.shift()) { 244 | p.resolve() 245 | } 246 | loading = false 247 | change_data = false 248 | change_template = false 249 | }) 250 | } 251 | } 252 | 253 | function constructTemplate(template_header, template_process) { 254 | return '' 255 | } 256 | 257 | function getHtmlTemplateProcessFromServer(req_timestamp, req_template_type, req_change_data, req_change_template) { 258 | return new Promise((resolve, reject) => { 259 | if (loading) { 260 | queue.push({ resolve, reject }) 261 | } 262 | if (!loading) { 263 | loading = true 264 | 265 | if (req_change_template) { 266 | host = document.location.host; 267 | var url = "http://" + host + "/template?type=" + req_template_type; 268 | var request = new XMLHttpRequest(); 269 | 270 | request.open("GET", url, true); 271 | request.send(); 272 | request.onload = function () { 273 | if (request.status === 200) { 274 | raw_template_process = request.responseText; 275 | cache_template_process = raw_template_process 276 | resolve({ 277 | req_timestamp, 278 | req_change_data 279 | }) 280 | } 281 | } 282 | } else { 283 | resolve({ req_timestamp, req_change_data }) 284 | } 285 | } 286 | }) 287 | } 288 | 289 | function getHtmlTemplateHeaderFromServer(paras) { 290 | return new Promise((resolve, reject) => { 291 | if (init) { 292 | host = document.location.host; 293 | var url = "http://" + host + "/template_header" 294 | var request = new XMLHttpRequest(); 295 | 296 | var req_timestamp = paras.req_timestamp 297 | var req_change_data = paras.req_change_data 298 | 299 | request.open("GET", url, true); 300 | request.send(); 301 | request.onload = function () { 302 | if (request.status === 200) { 303 | raw_template_header = request.responseText; 304 | cache_template_header = raw_template_header 305 | resolve({ 306 | req_timestamp, 307 | req_change_data 308 | }) 309 | } 310 | } 311 | } else { 312 | resolve({ req_timestamp, req_change_data }) 313 | } 314 | }) 315 | } 316 | 317 | function getJsonRawDataFromServer(paras) { 318 | return new Promise((resolve, reject) => { 319 | if (paras.req_change_data) { 320 | host = document.location.host; 321 | var url = "http://" + host + "/showsamp?timestamp=" + paras.req_timestamp + "&lables=ALL"; 322 | var request = new XMLHttpRequest(); 323 | 324 | request.open("GET", url, true); 325 | request.send(); 326 | request.onload = function () { 327 | if (request.status === 200) { 328 | raw_json = request.responseText; 329 | cache_json_data = raw_json 330 | resolve() 331 | } 332 | } 333 | } else { 334 | resolve() 335 | } 336 | }) 337 | } 338 | 339 | function LoadHtmlFromJson(raw_json, template) { 340 | var parser = new DOMParser(); 341 | doc = parser.parseFromString(template, "text/html"); 342 | let tpl = doc.getElementById('tpl').innerHTML; 343 | let node = HtmlToNode(tpl); 344 | document.getElementById('target').innerHTML = ParseJsonToHtml(node, raw_json); 345 | } 346 | 347 | function ParseJsonToHtml(node, raw_json) { 348 | var nest_json = "[" + raw_json + "]"; 349 | var json = JSON.parse(nest_json); 350 | preprocessJson(json); 351 | 352 | // sec to us 353 | per_cpu_tot = json[0]["EXTRA"]["percputot"]; 354 | hertz = json[0]["CPU"]["hertz"]; 355 | return repeatAtopHtmlNode(node, json, per_cpu_tot, hertz); 356 | } 357 | 358 | function preprocessJson(json) { 359 | parseAtopHeader(json); 360 | parseAtopProcess(json, filter_str); 361 | } 362 | 363 | function repeatAtopHtmlNode(node, arr, percputot) { 364 | let out = []; 365 | 366 | for (let i = 0; i < arr.length; i++) { 367 | let tmp = node.outerHTML; 368 | tmp = tmp.replace(/\s/g, ' '); 369 | 370 | let map = arr[i]; 371 | // parse inner array 372 | for (let j in map) { 373 | if (map[j] instanceof Array) { 374 | let subNode = node.querySelector('.' + j); 375 | if (subNode) { 376 | let subHTML = repeatAtopHtmlNode(subNode, map[j], percputot, hertz); 377 | let subTpl = subNode.outerHTML.replace(/\s/g, ' '); 378 | tmp = tmp.replace(subTpl, subHTML); 379 | } 380 | } 381 | } 382 | 383 | // parse Object 384 | for (let j in map) { 385 | if (map[j] instanceof Object && !(map[j] instanceof Array)) { 386 | let subNode = node.querySelector('.' + j); 387 | if (subNode) { 388 | let subHTML = repeatAtopHtmlNode(subNode, [map[j]], percputot, hertz); 389 | let subTpl = subNode.outerHTML.replace(/\s/g, ' '); 390 | tmp = tmp.replace(subTpl, subHTML); 391 | } 392 | } 393 | } 394 | 395 | for (let j in map) { 396 | if (typeof map[j] === 'string' || typeof map[j] === 'number') { 397 | if (["stime", "utime", "ntime", "itime", "wtime", "Itime", "Stime", "steal", "guest"].indexOf(j) !== -1) { 398 | map[j] = ParseCPUPercentValue(map[j], percputot); 399 | } else if (["cpusome", "memsome", "memfull", "iosome", "iofull"].indexOf(j) !== -1) { 400 | map[j] = ParsePSIPercentValue(map[j]); 401 | } else if (["freq"].indexOf(j) !== -1) { 402 | map[j] = ParseCPUCurfValue(map[j]); 403 | } else if (["stime_unit_time", "utime_unit_time", "rundelay", "blkdelay"].indexOf(j) !== -1) { 404 | map[j] = ParseTimeValue(map[j], hertz, j); 405 | } else if (["buffermem", "cachedrt", "cachemem", "commitlim", "committed", "freemem", "freeswap", 406 | "filepage", "physmem", "rgrow", "rsz", "shmem", "shmrss", "shmswp", "slabmem", "swcac", "totmem", "totswap", "slabreclaim", "pagetables", 407 | "vexec", "vdata", "vgrow", "vlibs", "vlock", "vmem", "vstack", "wsz", "rmem", "pmem", "vswap", "tcpsk", "udpsk", "dirtymem", "active", 408 | "mbm_total", "mbm_local", "inactive"].indexOf(j) !== -1) { 409 | map[j] = ParseMemValue(map[j], j); 410 | } else if (["rbyte", "sbyte", "speed"].indexOf(j) !== -1) { 411 | map[j] = ParseBandwidth(map[j], j); 412 | } else if (["minflt", "majflt"].indexOf(j) !== -1) { 413 | map[j] = Value2ValueStr(map[j], j); 414 | } else if (["cpubusy", "membusy", "diskbusy", "lvmbusy", "frag", "occupancy"].indexOf(j) !== -1) { 415 | map[j] = Value2ValuePercent(map[j], j); 416 | } 417 | else { 418 | map[j] = map[j]; 419 | } 420 | 421 | if (typeof map[j] === 'string') { 422 | map[j] = map[j]; 423 | } 424 | 425 | let re = new RegExp('{' + j + '}', 'g'); 426 | tmp = tmp.replace(re, map[j]); 427 | } 428 | } 429 | 430 | out.push(tmp); 431 | } 432 | return out.join(''); 433 | } -------------------------------------------------------------------------------- /http/js/atop_parse.js: -------------------------------------------------------------------------------- 1 | function parseAtopHeader(json) { 2 | // Modified the global 3 | pre_timestamp = json[0]["timestamp"]; 4 | 5 | // Parse date 6 | var now = new Date(json[0]["timestamp"] * 1000); 7 | y = now.getFullYear(); 8 | m = now.getMonth() + 1; 9 | d = now.getDate(); 10 | format_date = y + "-" + (m < 10 ? "0" + m : m) + "-" + (d < 10 ? "0" + d : d) + " " + now.toTimeString().slice(0, 8); 11 | json[0]["date"] = format_date; 12 | 13 | // Parse elapsed 14 | elapsed = json[0]["elapsed"]; 15 | 16 | //CPU line 17 | CPUEntry = json[0]["CPU"]; 18 | CPU_Tot = CPUEntry["stime"] + CPUEntry["utime"] + CPUEntry["ntime"] + CPUEntry["itime"] + CPUEntry["wtime"] + CPUEntry["Itime"] + CPUEntry["Stime"] + CPUEntry["steal"]; 19 | 20 | if (CPU_Tot === 0) { 21 | CPU_Tot = 1; 22 | } 23 | 24 | per_cpu_tot = CPU_Tot / CPUEntry["nrcpu"]; 25 | mstot = CPU_Tot * 1000 / CPUEntry["hertz"] / CPUEntry["nrcpu"]; 26 | 27 | json[0]["CPU"]["cpubusy"] = ((CPU_Tot - CPUEntry["itime"] - CPUEntry["wtime"]) / per_cpu_tot * 100).toFixed(1); 28 | 29 | //cpu line 30 | cpulist = json[0]["cpu"]; 31 | cpulist.sort(compareOrderByCpu); 32 | 33 | var temp_cpu_show_num = cpu_show_num 34 | if (temp_cpu_show_num > cpulist.length) { 35 | temp_cpu_show_num = cpulist.length; 36 | } 37 | json[0]["cpu"] = cpulist.slice(0, temp_cpu_show_num); 38 | 39 | //GPU line 40 | //TODO 41 | 42 | //CPL line 43 | json[0]["CPL"]["nrcpu"] = CPUEntry["nrcpu"]; 44 | 45 | //MEM line 46 | 47 | //SWP line 48 | 49 | //NUM NUC line 50 | json[0]["MEM"]["numanode"] = json[0]["NUM"].length; 51 | 52 | numa_memlist = json[0]["NUM"]; 53 | numa_memlist.forEach(function (numanode, index) { 54 | numanode["numanodeId"] = index; 55 | numanode["frag"] = (numanode["frag"] * 1).toFixed(2) 56 | }) 57 | 58 | numa_cpulist = json[0]["NUC"]; 59 | var temp_numa_show_num = numa_show_num 60 | if (temp_numa_show_num > numa_cpulist.length) { 61 | temp_numa_show_num = numa_cpulist.length; 62 | } 63 | 64 | json[0]["NUM"] = numa_memlist.slice(0, temp_numa_show_num); 65 | json[0]["NUC"] = numa_cpulist.slice(0, temp_numa_show_num); 66 | 67 | //PAG line 68 | 69 | //PSI line 70 | psiEntry = json[0]["PSI"]; 71 | 72 | if (function(){ 73 | for(var i in psiEntry) { 74 | return false 75 | } 76 | return true 77 | } === false ) { 78 | json[0]["PSI"]["cpusome"] = ((psiEntry["cstot"] / elapsed * 100) > 100) ? 100 : psiEntry["cstot"] / (elapsed * 10000); 79 | json[0]["PSI"]["iosome"] = ((psiEntry["iostot"] / elapsed * 100) > 100) ? 100 : psiEntry["iostot"] / (elapsed * 10000); 80 | json[0]["PSI"]["iofull"] = ((psiEntry["ioftot"] / elapsed * 100) > 100) ? 100 : psiEntry["ioftot"] / (elapsed * 10000); 81 | json[0]["PSI"]["memsome"] = ((psiEntry["mstot"] / elapsed * 100) > 100) ? 100 : psiEntry["mstot"] / (elapsed * 10000); 82 | json[0]["PSI"]["memfull"] = ((psiEntry["mftot"] / elapsed * 100) > 100) ? 100 : psiEntry["mftot"] / (elapsed * 10000); 83 | json[0]["PSI"]["cs"] = psiEntry["cs10"].toFixed(0) + "/" + psiEntry["cs60"].toFixed(0) + "/" + psiEntry["cs300"].toFixed(0); 84 | json[0]["PSI"]["ms"] = psiEntry["ms10"].toFixed(0) + "/" + psiEntry["ms60"].toFixed(0) + "/" + psiEntry["ms300"].toFixed(0); 85 | json[0]["PSI"]["mf"] = psiEntry["mf10"].toFixed(0) + "/" + psiEntry["mf60"].toFixed(0) + "/" + psiEntry["mf300"].toFixed(0); 86 | json[0]["PSI"]["is"] = psiEntry["ios10"].toFixed(0) + "/" + psiEntry["ios60"].toFixed(0) + "/" + psiEntry["ios300"].toFixed(0); 87 | json[0]["PSI"]["if"] = psiEntry["iof10"].toFixed(0) + "/" + psiEntry["iof60"].toFixed(0) + "/" + psiEntry["iof300"].toFixed(0); 88 | } else { 89 | json[0]["PSI"] = [] 90 | } 91 | 92 | //LVM line 93 | lvmlist = json[0]["LVM"]; 94 | lvmlist.forEach(function (lvm, index) { 95 | lvm["lvmname"] = lvm["lvmname"].substring(lvm["lvmname"].length - 10) 96 | iotot = lvm["nread"] + lvm["nwrite"]; 97 | lvm["avio"] = (iotot ? lvm["io_ms"] / iotot : 0.0).toFixed(2); 98 | lvm["lvmbusy"] = (mstot ? lvm["io_ms"] / mstot : 0).toFixed(2); 99 | lvm["MBr/s"] = (lvm["nrsect"] / 2 / 1024 / elapsed).toFixed(1); 100 | lvm["MBw/s"] = (lvm["nwsect"] / 2 / 1024 / elapsed).toFixed(1); 101 | }) 102 | 103 | //DSK line 104 | dsklist = json[0]["DSK"]; 105 | dsklist.forEach(function (dsk, index) { 106 | iotot = dsk["nread"] + dsk["nwrite"]; 107 | dsk["avio"] = (iotot ? dsk["io_ms"] / iotot : 0.0).toFixed(2); 108 | dsk["diskbusy"] = (mstot ? dsk["io_ms"] / mstot : 0).toFixed(2); 109 | dsk["MBr/s"] = (dsk["nrsect"] / 2 / 1024 / elapsed).toFixed(1); 110 | dsk["MBw/s"] = (dsk["nwsect"] / 2 / 1024 / elapsed).toFixed(1); 111 | }) 112 | dsklist.sort(compareOrderByDiskIoms); 113 | var temp_disk_show_num = disk_show_num 114 | if (temp_disk_show_num > dsklist.length) { 115 | temp_disk_show_num = dsklist.length; 116 | } 117 | json[0]["DSK"] = dsklist.slice(0, temp_disk_show_num); 118 | 119 | //NET line 120 | netlist = json[0]["NET"]; 121 | if (netlist !== undefined){ 122 | netlist.sort(compareOrderByNet); 123 | var temp_interface_show_num = interface_show_num 124 | if (temp_interface_show_num > netlist.length) { 125 | temp_interface_show_num = netlist.length 126 | } 127 | json[0]["NET"] = netlist.slice(0, temp_interface_show_num); 128 | } 129 | 130 | //IFB line 131 | // TODO 132 | 133 | // LLC line 134 | llclist = json[0]["LLC"]; 135 | if (llclist !== undefined){ 136 | llclist.sort(compareOrderLLC); 137 | json[0]["LLC"] = llclist.slice(0, LLC_show_num) 138 | } 139 | 140 | // EXTRA 141 | extra = new Object(); 142 | extra["mstot"] = mstot; 143 | extra["percputot"] = per_cpu_tot; 144 | 145 | extra["availmem"] = json[0]["MEM"]["physmem"] / 1024; 146 | json[0]["EXTRA"] = extra; 147 | } 148 | 149 | function parseAtopProcess(json, filter_str) { 150 | percputot = json[0]["EXTRA"]["percputot"]; 151 | availmem = json[0]["EXTRA"]["availmem"]; 152 | 153 | prc = json[0]["PRC"]; 154 | prm = json[0]["PRM"]; 155 | prd = json[0]["PRD"]; 156 | prg = json[0]["PRG"]; 157 | 158 | prcmap = ArrayToMap(prc); 159 | prmmap = ArrayToMap(prm); 160 | prdmap = ArrayToMap(prd); 161 | prgmap = ArrayToMap(prg); 162 | 163 | hprc = new Object(); 164 | hprc["proc_count"] = 0; 165 | hprc["sleep_count"] = 0; 166 | hprc["zombie_count"] = 0; 167 | hprc["exit_count"] = 0; 168 | hprc["running_count"] = 0; 169 | hprc["sleep_interrupt_count"] = 0; 170 | hprc["sleep_uninterrupt_count"] = 0; 171 | 172 | hprc["stime_unit_time"] = 0; 173 | hprc["utime_unit_time"] = 0; 174 | 175 | prgmap.forEach(function (iprg, index) { 176 | if (iprg["state"] == "E") { 177 | hprc["exit_count"]++; 178 | } else { 179 | if (iprg["state"] == "Z") { 180 | hprc["zombie_count"]++; 181 | } 182 | hprc["sleep_interrupt_count"] += iprg["nthrslpi"]; 183 | hprc["sleep_uninterrupt_count"] += iprg["nthrslpu"]; 184 | hprc["running_count"] += iprg["nthrrun"]; 185 | hprc["proc_count"]++; 186 | } 187 | }) 188 | 189 | // get availdsk 190 | var availdisk = 0; 191 | prdmap.forEach(function (iprd, index) { 192 | var nett_wsz = 0; 193 | if (iprd["wsz"] > iprd["cwsz"]) { 194 | nett_wsz = iprd["wsz"] - iprd["cwsz"]; 195 | } else { 196 | nett_wsz = 0; 197 | } 198 | 199 | availdisk += iprd["rsz"]; 200 | availdisk += nett_wsz; 201 | }) 202 | 203 | // filter str 204 | aggregate_pr_map = new Map(); 205 | prgmap.forEach(function (iprg, index) { 206 | iprm = prmmap.get(index); 207 | iprd = prdmap.get(index); 208 | iprc = prcmap.get(index); 209 | 210 | // proc_count not contains exit proc 211 | if (iprg["isproc"] !== 1) { 212 | iprg["tid"] = iprg["tgid"]; 213 | if (only_proc) { 214 | return; 215 | } 216 | } else { 217 | iprg["tid"] = '-'; 218 | } 219 | 220 | if (typeof filter_str == 'string') { 221 | if (filter_str != "") { 222 | if (iprg["name"].indexOf(filter_str) == -1) { 223 | return 224 | } 225 | } 226 | } 227 | 228 | hprc["stime_unit_time"] += iprc["stime"]; 229 | hprc["utime_unit_time"] += iprc["utime"]; 230 | 231 | //cpu 232 | cputot = iprc["stime"] + iprc["utime"]; 233 | iprc["cpubusy"] = ((cputot / percputot) * 100).toFixed(1); 234 | 235 | iprc["stime_unit_time"] = iprc["stime"]; 236 | iprc["utime_unit_time"] = iprc["utime"]; 237 | iprc["curcpu"] = iprc["curcpu"] ? iprc["curcpu"] : "-"; 238 | 239 | //general 240 | pro_name = iprg["name"].replace(/[()]/ig, ""); 241 | iprg["name"] = pro_name.length > 15 ? pro_name.substr(0, 15) : pro_name; 242 | cmdline = iprg["cmdline"].replace(/[()]/ig, ""); 243 | if (cmdline.length == 0) { 244 | iprg["cmdline"] = iprg["name"] 245 | } else { 246 | iprg["cmdline"] = cmdline 247 | } 248 | iprg["exitcode"] = iprg["exitcode"] ? iprg["exitcode"] : "-"; 249 | 250 | //mem 251 | membusy = (iprm["rmem"] * 100 / availmem).toFixed(1) 252 | if (membusy > 100) { 253 | iprm["membusy"] = 100; 254 | } else { 255 | iprm["membusy"] = membusy; 256 | } 257 | 258 | iprm["vexec"] = iprm["vexec"] * 1024; 259 | iprm["vlibs"] = iprm["vlibs"] * 1024; 260 | iprm["vdata"] = iprm["vdata"] * 1024; 261 | iprm["vstack"] = iprm["vstack"] * 1024; 262 | iprm["vlock"] = iprm["vlock"] * 1024; 263 | iprm["vmem"] = iprm["vmem"] * 1024; 264 | iprm["rmem"] = iprm["rmem"] * 1024; 265 | iprm["pmem"] = iprm["pmem"] * 1024; 266 | iprm["vgrow"] = iprm["vgrow"] * 1024; 267 | iprm["rgrow"] = iprm["rgrow"] * 1024; 268 | iprm["vswap"] = iprm["vswap"] * 1024; 269 | 270 | //disk 271 | var nett_wsz = 0; 272 | if (iprd["wsz"] > iprd["cwsz"]) { 273 | nett_wsz = iprd["wsz"] - iprd["cwsz"]; 274 | } else { 275 | nett_wsz = 0; 276 | } 277 | 278 | diskbusy = ((iprd["rsz"] + nett_wsz) * 100 / availdisk).toFixed(1) 279 | if (diskbusy > 100) { 280 | iprd["diskbusy"] = 100; 281 | } else { 282 | iprd["diskbusy"] = diskbusy; 283 | } 284 | 285 | iprd["rsz"] = iprd["rsz"] * 512; 286 | iprd["wsz"] = iprd["wsz"] * 512; 287 | iprd["cwsz"] = iprd["cwsz"] * 512; 288 | 289 | aggregate_pr = Object.assign(iprc, iprm, iprd, iprg); 290 | 291 | aggregate_pr_map.set(index, aggregate_pr); 292 | }) 293 | 294 | // map to arr for sort 295 | aggregate_pr_arr = []; 296 | aggregate_pr_map.forEach(function (value, key) { 297 | aggregate_pr_arr.push(value); 298 | }) 299 | 300 | switch (order_type) { 301 | case OrderType.CPU: 302 | aggregate_pr_arr.sort(compareOrderByCpu); 303 | break; 304 | case OrderType.DISK: 305 | aggregate_pr_arr.sort(compareOrderByDisk); 306 | break; 307 | case OrderType.MEM: 308 | aggregate_pr_arr.sort(compareOrderByMem); 309 | break; 310 | default: 311 | aggregate_pr_arr.sort(compareOrderByCpu); 312 | } 313 | 314 | var temp_proc_show_num = proc_show_num 315 | if (temp_proc_show_num > aggregate_pr_arr.length) { 316 | temp_proc_show_num = aggregate_pr_arr.length; 317 | } 318 | 319 | json[0]["PRSUMMARY"] = aggregate_pr_arr.slice(0, temp_proc_show_num); 320 | json[0]["HPRC"] = hprc; 321 | delete json[0]["PRC"]; 322 | delete json[0]["PRM"]; 323 | delete json[0]["PRD"]; 324 | delete json[0]["PRG"]; 325 | } 326 | 327 | function ParseCPUPercentValue(value, percputot) { 328 | return Math.round(value * 100 / percputot).toFixed(0) + "%"; 329 | } 330 | 331 | function ParseCPUCurfValue(value) { 332 | if (value < 1000) { 333 | return value + "MHz"; 334 | } else { 335 | fval = value / 1000; 336 | prefix = 'G'; 337 | if (fval >= 1000) { 338 | prefix = 'T'; 339 | fval = fval / 1000; 340 | } 341 | 342 | if (fval < 10) { 343 | return fval.toFixed(2) + prefix + "Hz"; 344 | } else { 345 | if (fval < 100) { 346 | return fval.toFixed(1) + prefix + "Hz"; 347 | } else { 348 | return fval.toFixed(0) + prefix + "Hz"; 349 | } 350 | } 351 | } 352 | } 353 | 354 | function ParsePSIPercentValue(value) { 355 | return Math.round(value).toFixed(0) + "%"; 356 | } 357 | 358 | function ParseTimeValue(value, hertz, indicatorName) { 359 | // here is ms 360 | value = parseInt(value * 1000 / hertz); 361 | 362 | if (value < 100000) { 363 | return parseInt(value / 1000) + "." + parseInt(value % 1000 / 10) + "s"; 364 | } else { 365 | //ms to sec 366 | value = parseInt((value + 500) / 1000); 367 | if (value < 6000) { 368 | //sec to min 369 | return parseInt(value / 60) + "m" + (value % 60) + "s"; 370 | } else { 371 | //min to hour 372 | value = parseInt((value + 30) / 60); 373 | if (value < 6000) { 374 | return parseInt(value / 60) + "h" + (value % 60) + "m"; 375 | } else { 376 | // hour to day 377 | value = parseInt((value + 30) / 60); 378 | return parseInt(value / 24) + "d" + (value % 24) + "h"; 379 | } 380 | } 381 | } 382 | } 383 | 384 | function ParseMemValue(ByteValue, indicatorName) { 385 | var unit = 1024; // byte 386 | ByteValue = ByteValue - 0; 387 | 388 | var prefix = ""; 389 | if (ByteValue < 0) { 390 | ByteValue = -ByteValue * 10; 391 | var prefix = "-"; 392 | } 393 | 394 | if (ByteValue < unit) { 395 | result = ByteValue + 'B'; 396 | } else if (ByteValue < Math.pow(unit, 2) * 0.8) { 397 | result = (ByteValue / unit).toFixed(1) + "KB"; 398 | } else if (ByteValue < Math.pow(unit, 3) * 0.8) { 399 | result = (ByteValue / Math.pow(unit, 2)).toFixed(1) + "MB"; 400 | } else if (ByteValue < Math.pow(unit, 4) * 0.8) { 401 | result = (ByteValue / Math.pow(unit, 3)).toFixed(1) + "G"; 402 | } else { 403 | result = (ByteValue / Math.pow(unit, 4)).toFixed(1) + "T"; 404 | } 405 | 406 | return prefix + result; 407 | } 408 | 409 | function ParseBandwidth(BandwidthValue, indicatorName) { 410 | var unit = 1000; 411 | 412 | if (BandwidthValue < unit) { 413 | return BandwidthValue + "Kbps"; 414 | } else if (BandwidthValue < Math.pow(unit, 2)) { 415 | return (BandwidthValue / unit).toFixed(2) + "Mbps"; 416 | } else if (BandwidthValue < Math.pow(unit, 3)) { 417 | return (BandwidthValue / Math.pow(unit, 2)).toFixed(2) + "Gbps"; 418 | } else { 419 | return (BandwidthValue / Math.pow(unit, 3)).toFixed(2) + "Tbps"; 420 | } 421 | } 422 | 423 | // for number toooo long 424 | function Value2ValueStr(value, indicatorName) { 425 | // TODO 426 | return value; 427 | } 428 | 429 | function Value2ValuePercent(value, indicatorName) { 430 | return value + "%"; 431 | } 432 | 433 | function ArrayToMap(array) { 434 | map = new Map(); 435 | for (i of array) { 436 | map.set(i["pid"], i); 437 | } 438 | return map; 439 | } 440 | 441 | function HtmlToNode(html) { 442 | let div = document.createElement('div'); 443 | let pos = html.indexOf('<'); 444 | div.innerHTML = html.substring(pos); 445 | return div.firstChild; 446 | } 447 | 448 | function ParseDateToTimesample(itim) { 449 | ilen = itim.length 450 | if (ilen != 4 && ilen != 5 && ilen != 12 && ilen != 13) { 451 | return 0; 452 | } 453 | 454 | var year, month, day, hour, minute 455 | 456 | if (ilen == 12 || ilen == 13) { 457 | year = itim.slice(0, 4); 458 | month = itim.slice(4, 6); 459 | day = itim.slice(6, 8); 460 | hour = itim.slice(8, 10); 461 | minute = itim.slice(-2); 462 | } else if (ilen == 4 || ilen == 5) { 463 | now_date = new Date(); 464 | year = now_date.getFullYear(); 465 | month = now_date.getMonth() + 1; 466 | day = now_date.getDate(); 467 | 468 | hour = itim.slice(0, 2); 469 | minute = itim.slice(-2); 470 | } 471 | 472 | if (year < 100 || month < 0 || month > 12 || day < 1 || day > 31 || hour < 0 || hour > 23 || minute < 0 || minute > 59) { 473 | return 0; 474 | } 475 | 476 | parsed_timestamp = Date.parse(new Date(year, month - 1, day, hour, minute)) / 1000; 477 | return parsed_timestamp; 478 | } 479 | 480 | Date.prototype.format = function (fmt) { 481 | let ret; 482 | const opt = { 483 | "Y+": this.getFullYear().toString(), 484 | "M+": (this.getMonth() + 1).toString(), 485 | "D+": this.getDate().toString(), 486 | "h+": this.getHours().toString(), 487 | "m+": this.getMinutes().toString(), 488 | }; 489 | for (let k in opt) { 490 | ret = new RegExp("(" + k + ")").exec(fmt); 491 | if (ret) { 492 | fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0"))) 493 | }; 494 | }; 495 | return fmt; 496 | } 497 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /httpd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 zhenwei pi 3 | * 4 | * Authors: 5 | * zhenwei pi 6 | * 7 | * This work is licensed under the terms of the GNU GPL, version 2 or later. 8 | * See the COPYING file in the top-level directory. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "httpd.h" 33 | #include "output.h" 34 | 35 | #include "version.h" 36 | 37 | #define DEFAULT_PORT 2867 38 | #define DEFAULT_TLS_PORT 2868 39 | #define DEFAULT_LOG_PATH "/var/log/atop" 40 | 41 | #define DEFAULT_CERT_FILE "/etc/pki/atophttpd/server.crt" 42 | #define DEFAULT_KEY_FILE "/etc/pki/atophttpd/server.key" 43 | #define DEFAULT_CA_FILE "/etc/pki/CA/ca.crt" 44 | 45 | static void http_show_samp_done(struct output *op, connection *conn); 46 | 47 | struct output defop = { 48 | .output_type = OUTPUT_BUF, 49 | .done = http_show_samp_done 50 | }; 51 | 52 | static struct atophttd_context config = { 53 | .port = DEFAULT_PORT, 54 | .daemonmode = 0, 55 | .log_path = DEFAULT_LOG_PATH, 56 | .addr = "127.0.0.1", 57 | 58 | .tls_ctx_config.tls_port = -1, 59 | .tls_ctx_config.tls_addr = "::*", 60 | .tls_ctx_config.ca_cert_file = DEFAULT_CA_FILE, 61 | .tls_ctx_config.cert_file = DEFAULT_CERT_FILE, 62 | .tls_ctx_config.key_file = DEFAULT_KEY_FILE, 63 | }; 64 | 65 | 66 | unsigned int pagesize; 67 | unsigned short hertz; 68 | struct utsname utsname; 69 | int hidecmdline = 0; 70 | 71 | #define INBUF_SIZE 4096 72 | #define URL_LEN 1024 73 | /* HTTP codes */ 74 | static char *http_200 = "HTTP/1.1 200 OK\r\n"; 75 | static char *http_404 = "HTTP/1.1 404 Not Found\r\n"; 76 | 77 | /* HTTP content types */ 78 | static char *http_content_type_none = ""; 79 | static char *http_content_type_deflate = "Content-Encoding: deflate\r\n"; 80 | 81 | /* HTTP generic header */ 82 | static char *http_generic = "Server: atop\r\n" 83 | "%s" /* for http_content_type_XXX */ 84 | "Content-Type: %s; charset=utf-8\r\n" 85 | "Content-Length: %d\r\n\r\n"; 86 | 87 | /* HTTP content types */ 88 | static char *http_content_type_html = "text/html"; 89 | static char *http_content_type_css = "text/css"; 90 | static char *http_content_type_javascript = "application/javascript"; 91 | 92 | static int http_prepare_response(connection *conn) 93 | { 94 | /* try to send response data in a timeout */ 95 | int onoff = 1; 96 | int ret; 97 | ret = setsockopt(conn->fd, IPPROTO_TCP, TCP_NODELAY, &onoff, sizeof(onoff)); 98 | if (ret) { 99 | printf("failed to set socket to TCP_NODELAY\n"); 100 | return ret; 101 | } 102 | 103 | ret = fcntl(conn->fd, F_SETFL, fcntl(conn->fd, F_GETFL) & ~O_NONBLOCK); 104 | if (ret) { 105 | printf("failed to set conn to blocking\n"); 106 | return ret; 107 | } 108 | 109 | struct timeval tv = {.tv_sec = 5, .tv_usec = 0}; 110 | ret = setsockopt(conn->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); 111 | if (ret) { 112 | printf("failed to set socket to SNDTIMEO\n"); 113 | return ret; 114 | } 115 | 116 | return 0; 117 | } 118 | 119 | static void http_response_200(connection *conn, char *buf, size_t len, char *encoding, char* content_type) 120 | { 121 | struct iovec iovs[3], *iov; 122 | int ret; 123 | char content[128] = {0}; 124 | int content_length = 0; 125 | 126 | ret = http_prepare_response(conn); 127 | if (ret) { 128 | goto closeconn; 129 | } 130 | 131 | /* 1, http code */ 132 | iov = &iovs[0]; 133 | iov->iov_base = http_200; 134 | iov->iov_len = strlen(http_200); 135 | 136 | /* 2, http generic content */ 137 | iov = &iovs[1]; 138 | 139 | content_length = sprintf(content, http_generic, encoding, content_type, len); 140 | iov->iov_base = content; 141 | iov->iov_len = content_length; 142 | 143 | /* 3, sample data record */ 144 | iov = &iovs[2]; 145 | iov->iov_base = buf; 146 | iov->iov_len = len; 147 | 148 | conn_writev(conn, iovs, sizeof(iovs) / sizeof(iovs[0])); 149 | 150 | closeconn: 151 | conn_close(conn); 152 | } 153 | 154 | static void http_show_samp_done(struct output *op, connection *conn) 155 | { 156 | if (op->encoding == http_content_type_none) { 157 | http_response_200(conn, op->ob.buf, op->ob.offset, op->encoding, http_content_type_html); 158 | return; 159 | } 160 | 161 | /* compress data for encoding deflate */ 162 | char *compbuf = malloc(op->ob.offset); 163 | unsigned long complen = op->ob.offset; 164 | 165 | if (compress((Bytef *)compbuf, &complen, (Bytef *)op->ob.buf, op->ob.offset) != Z_OK){ 166 | http_prepare_response(conn); 167 | conn_write(conn, http_404, strlen(http_404)); 168 | } 169 | 170 | http_response_200(conn, compbuf, complen, http_content_type_deflate, http_content_type_html); 171 | free(compbuf); 172 | } 173 | 174 | static int http_arg_long(char *req, char *needle, long *l) 175 | { 176 | char *s = strstr(req, needle); 177 | if (!s) 178 | return -1; 179 | 180 | s += strlen(needle); 181 | if (*s != '=') 182 | return -1; 183 | 184 | *l = atol(++s); 185 | 186 | return 0; 187 | } 188 | 189 | static int http_arg_str(char *req, char *needle, char *p, int len) 190 | { 191 | char *s = strstr(req, needle); 192 | if (!s) 193 | return -1; 194 | 195 | s += strlen(needle); 196 | if (*s++ != '=') 197 | return -1; 198 | 199 | char *e = strchr(s, '&'); 200 | if (!e) 201 | e = s + strlen(s); 202 | 203 | if (e - s > len) 204 | return -1; 205 | 206 | memcpy(p, s, e - s); 207 | p[e - s] = '\0'; 208 | while ((p = strstr(p, "%2C")) != NULL) 209 | { 210 | *(p++) = ','; 211 | memmove(p, p + 2, strlen(p)); 212 | } 213 | 214 | return 0; 215 | } 216 | 217 | static void http_showsamp(char *req, connection *conn) 218 | { 219 | time_t timestamp = 0; 220 | char lables[1024]; 221 | char encoding[16]; 222 | 223 | if (http_arg_long(req, "timestamp", ×tamp) < 0) { 224 | char *err = "missing timestamp\r\n"; 225 | http_response_200(conn, err, strlen(err), http_content_type_none, http_content_type_html); 226 | return; 227 | } 228 | 229 | if (http_arg_str(req, "lables", lables, sizeof(lables)) < 0) { 230 | char *err = "missing lables\r\n"; 231 | http_response_200(conn, err, strlen(err), http_content_type_none, http_content_type_html); 232 | return; 233 | } 234 | 235 | defop.encoding = http_content_type_deflate; 236 | if (http_arg_str(req, "encoding", encoding, sizeof(encoding)) == 0) { 237 | if (!strcmp(encoding, "none")) { 238 | defop.encoding = http_content_type_none; 239 | } else if (!strcmp(encoding, "deflate")) { 240 | defop.encoding = http_content_type_deflate; 241 | } else { 242 | char *err = "encoding supports none/deflate only\r\n"; 243 | http_response_200(conn, err, strlen(err), http_content_type_none, http_content_type_html); 244 | return; 245 | } 246 | } 247 | 248 | if (rawlog_get_record(timestamp, lables, conn) < 0) { 249 | char *err = "missing sample\r\n"; 250 | http_response_200(conn, err, strlen(err), http_content_type_none, http_content_type_html); 251 | return; 252 | } 253 | } 254 | 255 | /* Import a binary file */ 256 | #define IMPORT_BIN(sect, file, sym) asm ( \ 257 | ".section " #sect "\n" \ 258 | ".global " #sym "\n" \ 259 | ".global " #sym "_end\n" \ 260 | #sym ":\n" \ 261 | ".incbin \"" file "\"\n" \ 262 | #sym "_end:\n" \ 263 | ".section \".text\"\n") 264 | 265 | /* Build help.html into atop binary */ 266 | IMPORT_BIN(".rodata", "http/help.html", help); 267 | extern char help[], help_end[]; 268 | 269 | static void http_help(connection *conn) 270 | { 271 | http_response_200(conn, help, help_end - help, http_content_type_none, http_content_type_html); 272 | } 273 | 274 | /* Build favicon.ico into atop binary */ 275 | IMPORT_BIN(".rodata", "http/favicon.ico", favicon); 276 | extern char favicon[], favicon_end[]; 277 | 278 | static void http_favicon(connection *conn) 279 | { 280 | http_response_200(conn, favicon, favicon_end - favicon, http_content_type_none, http_content_type_html); 281 | } 282 | 283 | /* Build index.html into atop binary */ 284 | IMPORT_BIN(".rodata", "http/index.html", http_index_html); 285 | extern char http_index_html[], http_index_html_end[]; 286 | 287 | static void http_index(connection *conn) 288 | { 289 | http_response_200(conn, http_index_html, http_index_html_end - http_index_html, http_content_type_none, http_content_type_html); 290 | } 291 | 292 | /* Build atop.js into atop binary */ 293 | IMPORT_BIN(".rodata", "http/js/atop.js", atop_js); 294 | extern char atop_js[], atop_js_end[]; 295 | 296 | static void http_get_atop_js(connection *conn) 297 | { 298 | http_response_200(conn, atop_js, atop_js_end - atop_js, http_content_type_none, http_content_type_javascript); 299 | } 300 | 301 | /* Build atop_parse.js into atop binary */ 302 | IMPORT_BIN(".rodata", "http/js/atop_parse.js", atop_parse_js); 303 | extern char atop_parse_js[], atop_parse_js_end[]; 304 | 305 | static void http_get_atop_parse_js(connection *conn) 306 | { 307 | http_response_200(conn, atop_parse_js, atop_parse_js_end - atop_parse_js, http_content_type_none, http_content_type_javascript); 308 | } 309 | 310 | /* Build atop_compare_fc.js into atop binary */ 311 | IMPORT_BIN(".rodata", "http/js/atop_compare_fc.js", atop_compare_fc_js); 312 | extern char atop_compare_fc_js[], atop_compare_fc_js_end[]; 313 | 314 | static void http_get_atop_compare_fc_js(connection *conn) 315 | { 316 | http_response_200(conn, atop_compare_fc_js, atop_compare_fc_js_end - atop_compare_fc_js, http_content_type_none, http_content_type_javascript); 317 | } 318 | 319 | /* Build atop.css into atop binary */ 320 | IMPORT_BIN(".rodata", "http/css/atop.css", http_css); 321 | extern char http_css[], http_css_end[]; 322 | 323 | static void http_get_css(connection *conn) 324 | { 325 | http_response_200(conn, http_css, http_css_end - http_css, http_content_type_none, http_content_type_css); 326 | } 327 | 328 | /* Build template.html into atop binary */ 329 | IMPORT_BIN(".rodata", "http/template/html/header.html", header_html_template); 330 | extern char header_html_template[], header_html_template_end[]; 331 | 332 | IMPORT_BIN(".rodata", "http/template/html/generic.html", generic_html_template); 333 | extern char generic_html_template[], generic_html_template_end[]; 334 | 335 | IMPORT_BIN(".rodata", "http/template/html/memory.html", memory_html_template); 336 | extern char memory_html_template[], memory_html_template_end[]; 337 | 338 | IMPORT_BIN(".rodata", "http/template/html/disk.html", disk_html_template); 339 | extern char disk_html_template[], disk_html_template_end[]; 340 | 341 | IMPORT_BIN(".rodata", "http/template/html/command_line.html", command_line_html_template); 342 | extern char command_line_html_template[], command_line_html_template_end[]; 343 | 344 | static void http_get_template_header(connection *conn) 345 | { 346 | http_response_200(conn, header_html_template, header_html_template_end - header_html_template, http_content_type_none, http_content_type_html); 347 | } 348 | 349 | static void http_get_template(char *req, connection *conn) 350 | { 351 | char template_type[256]; 352 | if (http_arg_str(req, "type", template_type, sizeof(template_type)) < 0) 353 | return; 354 | 355 | if (!strcmp(template_type, "generic")) { 356 | http_response_200(conn, generic_html_template, generic_html_template_end - generic_html_template, http_content_type_none, http_content_type_html); 357 | } else if (!strcmp(template_type, "memory")) { 358 | http_response_200(conn, memory_html_template, memory_html_template_end - memory_html_template, http_content_type_none, http_content_type_html); 359 | } else if (!strcmp(template_type, "disk")) { 360 | http_response_200(conn, disk_html_template, disk_html_template_end - disk_html_template, http_content_type_none, http_content_type_html); 361 | } else if (!strcmp(template_type, "command_line")) { 362 | http_response_200(conn, command_line_html_template, command_line_html_template_end - command_line_html_template, http_content_type_none, http_content_type_html); 363 | } else { 364 | http_prepare_response(conn); 365 | conn_write(conn, http_404, strlen(http_404)); 366 | } 367 | } 368 | 369 | static void http_ping(connection *conn) 370 | { 371 | char *pong = "pong\r\n"; 372 | 373 | http_response_200(conn, pong, strlen(pong), http_content_type_none, http_content_type_html); 374 | } 375 | 376 | static void http_process_request(char *req, connection *conn) 377 | { 378 | char location[URL_LEN] = {0}; 379 | char *c; 380 | 381 | if (strlen(req) > URL_LEN) { 382 | http_prepare_response(conn); 383 | conn_write(conn, http_404, strlen(http_404)); 384 | return; 385 | } 386 | 387 | c = strchr(req, '?'); 388 | if (c) 389 | memcpy(location, req, c - req); 390 | else 391 | memcpy(location, req, strlen(req)); 392 | 393 | if (strlen(location) == 0) { 394 | http_index(conn); 395 | return; 396 | } 397 | 398 | if (!strcmp(location, "ping")) 399 | http_ping(conn); 400 | else if (!strcmp(location, "help")) 401 | http_help(conn); 402 | else if (!strcmp(location, "favicon.ico")) 403 | http_favicon(conn); 404 | else if (!strcmp(location, "showsamp")) 405 | http_showsamp(req, conn); 406 | else if (!strcmp(location, "index.html")) 407 | http_index(conn); 408 | else if (!strcmp(location, "js/atop.js")) 409 | http_get_atop_js(conn); 410 | else if (!strcmp(location, "js/atop_parse.js")) 411 | http_get_atop_parse_js(conn); 412 | else if (!strcmp(location, "js/atop_compare_fc.js")) 413 | http_get_atop_compare_fc_js(conn); 414 | else if (!strcmp(location, "css/atop.css")) 415 | http_get_css(conn); 416 | else if (!strcmp(location, "template_header")) 417 | http_get_template_header(conn); 418 | else if (!strcmp(location, "template")) 419 | http_get_template(req, conn); 420 | else { 421 | http_prepare_response(conn); 422 | conn_write(conn, http_404, strlen(http_404)); 423 | } 424 | } 425 | 426 | static time_t httpd_now_ms() 427 | { 428 | struct timeval now; 429 | 430 | gettimeofday(&now, NULL); 431 | 432 | return now.tv_sec * 1000 + now.tv_usec / 1000; 433 | } 434 | 435 | static void httpd_handle_request(connection *conn) { 436 | char inbuf[INBUF_SIZE] = {0}; 437 | int inbytes = 0; 438 | char httpreq[URL_LEN] = {0}; 439 | time_t timeout = httpd_now_ms() + 100; 440 | int ret; 441 | char *httpver; 442 | 443 | ret = fcntl(conn->fd, F_SETFL, fcntl(conn->fd, F_GETFL) | O_NONBLOCK); 444 | if (ret) { 445 | printf("failed to set conn to non_blocking"); 446 | goto close_fd; 447 | } 448 | 449 | for ( ; ; ) { 450 | time_t now = httpd_now_ms(); 451 | if (now >= timeout) 452 | goto close_fd; 453 | 454 | struct pollfd pfd = {.fd = conn->fd, .events = POLLIN, .revents = 0}; 455 | ret = poll(&pfd, 1, timeout - now); 456 | if (ret <= 0) 457 | { 458 | if (ret != 0) { 459 | goto close_fd; 460 | } 461 | continue; 462 | } 463 | 464 | ret = conn_read(conn, inbuf + inbytes, sizeof(inbuf) - inbytes); 465 | if (ret < 0) 466 | { 467 | if (ret != -EAGAIN) { 468 | goto close_fd; 469 | } 470 | continue; 471 | } 472 | 473 | inbytes += ret; 474 | if ((inbytes >= 4) && strstr(inbuf, "\r\n\r\n")) 475 | break; 476 | 477 | /* buf is full, but we can not search the end of HTTP header */ 478 | if (inbytes == sizeof(inbuf)) 479 | goto close_fd; 480 | } 481 | 482 | /* support GET request only */ 483 | if (strncmp("GET ", inbuf, 4)) 484 | goto close_fd; 485 | 486 | /* support HTTP 1.1 request only */ 487 | httpver = strstr(inbuf, "HTTP/1.1"); 488 | if (!httpver) 489 | goto close_fd; 490 | 491 | /* Ex, GET /hello HTTP/1.1 */ 492 | if ((httpver - inbuf > URL_LEN + 6) || (httpver - inbuf < 6)) 493 | goto close_fd; 494 | 495 | memcpy(httpreq, inbuf + 5, httpver - inbuf - 6); 496 | http_process_request(httpreq, conn); 497 | 498 | close_fd: 499 | conn_close(conn); 500 | return; 501 | } 502 | 503 | static void httpd_update_cache(char *log_path) 504 | { 505 | static time_t update; 506 | time_t now = time(NULL); 507 | 508 | if (now - update < 3) 509 | return; 510 | 511 | if (rawlog_parse_all(log_path)) { 512 | printf("%s: rawlog parse failed\n", __func__); 513 | } 514 | 515 | update = now; 516 | } 517 | 518 | static void *httpd_routine(connection **listeners, char *log_path) 519 | { 520 | int epollfd; 521 | int ret = 0; 522 | int nr_listener = 0; 523 | connection *listener; 524 | struct epoll_event event; 525 | 526 | epollfd = epoll_create1(0); 527 | if (epollfd < 0) { 528 | printf("Failed create epollfd\n"); 529 | exit(1); 530 | } 531 | 532 | for (int i = 0; i < CONN_TYPE_MAX; i++) { 533 | listener = listeners[i]; 534 | if (listener == NULL) { 535 | continue; 536 | } 537 | 538 | ret = conn_listen(listener); 539 | if (ret < 0) { 540 | printf("ConnectionType %s failed listening on port %u, aborting.\n", listener->type->get_type(NULL), listener->port); 541 | exit(1); 542 | } 543 | 544 | event.events = EPOLLIN; 545 | event.data.ptr = listener; 546 | if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listener->fd, &event)) { 547 | printf("Add listener into epoll failed\n"); 548 | exit(1); 549 | } 550 | nr_listener++; 551 | } 552 | 553 | if (nr_listener == 0) { 554 | printf("Listen Nothing, Exit.\n"); 555 | exit(1); 556 | } 557 | 558 | printf("Ready to serve\n"); 559 | while (1) { 560 | ret = epoll_wait(epollfd, &event, 1, 1000); 561 | if (!ret) { 562 | continue; 563 | } 564 | 565 | if (ret < 0) { 566 | if (errno == EINTR) { 567 | continue; 568 | } else { 569 | printf("Error in epoll_wait %m\n"); 570 | exit(1); 571 | } 572 | } 573 | 574 | listener = event.data.ptr; 575 | connection *conn; 576 | conn = conn_create(listener->type, -1, NULL); 577 | ret = conn_accept(listener, conn); 578 | if (ret < 0) { 579 | conn_close(conn); 580 | free(conn); 581 | continue; 582 | } 583 | 584 | httpd_handle_request(conn); 585 | httpd_update_cache(log_path); 586 | free(conn); 587 | } 588 | 589 | return NULL; 590 | } 591 | 592 | static int httpd(struct atophttd_context ctx) 593 | { 594 | signal(SIGPIPE, SIG_IGN); 595 | connection *listener; 596 | int conn_index, ret; 597 | 598 | if (ctx.port > 0) { 599 | conn_index = get_conntype_index_by_name(CONN_TYPE_SOCKET); 600 | if (conn_index < 0) { 601 | printf("Failed finding connectin listener of %s\n", CONN_TYPE_SOCKET); 602 | exit(1); 603 | } 604 | listener = conn_create(get_conntype_by_name(CONN_TYPE_SOCKET), ctx.port, ctx.addr); 605 | ctx.listeners[conn_index] = listener; 606 | } 607 | 608 | if (ctx.tls_ctx_config.tls_port > 0) { 609 | conn_index = get_conntype_index_by_name(CONN_TYPE_TLS); 610 | if (conn_index < 0) { 611 | exit(1); 612 | } 613 | listener = conn_create(get_conntype_by_name(CONN_TYPE_TLS), ctx.tls_ctx_config.tls_port, ctx.tls_ctx_config.tls_addr); 614 | 615 | ret = conn_configure(get_conntype_by_name(CONN_TYPE_TLS), &ctx.tls_ctx_config, 1); 616 | if (ret < 0) { 617 | printf("Failed to configure TLS\n"); 618 | exit(1); 619 | } 620 | 621 | ctx.listeners[conn_index] = listener; 622 | } 623 | 624 | if (ctx.daemonmode) 625 | daemon(0, 0); 626 | 627 | httpd_routine(ctx.listeners, ctx.log_path); 628 | return 0; 629 | } 630 | 631 | int __debug = 0; 632 | static char *short_opts = "dDhHp:a:P:t::A:C:c:k:V"; 633 | 634 | static struct option long_opts[] = { 635 | { "daemon", no_argument, 0, 'd' }, 636 | { "debug", no_argument, 0, 'D' }, 637 | { "port", required_argument, 0, 'p' }, 638 | { "addr", required_argument, 0, 'a' }, 639 | { "path", required_argument, 0, 'P' }, 640 | { "tls-port", optional_argument, 0, 't'}, 641 | { "tls-addr", required_argument, 0, 'A'}, 642 | { "ca-cert-file", required_argument, 0, 'C' }, 643 | { "cert-file", required_argument, 0, 'c' }, 644 | { "key-file", required_argument, 0, 'k' }, 645 | { "help", no_argument, 0, 'h' }, 646 | { "hide-cmdline", no_argument, 0, 'H' }, 647 | { "version", no_argument, 0, 'V' }, 648 | { 0, 0, 0, 0 } 649 | }; 650 | 651 | static void httpd_showhelp(void) 652 | { 653 | printf("Usage:\n"); 654 | printf(" -d/--daemon \n run in daemon mode\n"); 655 | printf(" -D/--debug \n run with debug message\n"); 656 | printf(" -p/--port PORT \n listen to PORT, default %d\n", DEFAULT_PORT); 657 | printf(" -a/--addr ADDR \n bind to ADDR, default bind local host\n"); 658 | printf(" -P/--path PATH \n atop log path, default %s\n", DEFAULT_LOG_PATH); 659 | printf(" -t/--tls-port PORT \n listen to TLS PORT, default %d\n", DEFAULT_TLS_PORT); 660 | printf(" -A/--tls-addr ADDR \n bind to TLS ADDR, default bind * (all addresses)\n"); 661 | printf(" -C/--ca-cert-file PATH\n Path to the server TLS trusted CA cert file, default %s\n", DEFAULT_CA_FILE); 662 | printf(" -c/--cert-file PATH \n Path to the server TLS cert file, default %s\n", DEFAULT_CERT_FILE); 663 | printf(" -k/--key-file PATH \n Path to the server TLS key file, default %s\n", DEFAULT_KEY_FILE); 664 | printf(" -H/--hide-cmdline \n hide cmdline for security protection\n"); 665 | printf(" -h/--help \n show help\n\n"); 666 | printf(" maintained by \n zhenwei pi (HTTP backend)\n"); 667 | printf(" enhua zhou (HTTP frontend)\n"); 668 | exit(0); 669 | } 670 | 671 | static void httpd_showversion(void) 672 | { 673 | printf("Version: %s\n", ATOPVERS); 674 | exit(0); 675 | } 676 | 677 | int main(int argc, char *argv[]) 678 | { 679 | int args, errno; 680 | int ch; 681 | 682 | while (1) { 683 | ch = getopt_long(argc, argv, short_opts, long_opts, &args); 684 | if (ch == -1) { 685 | break; 686 | } 687 | switch (ch) { 688 | case 0: 689 | break; 690 | case 'd': 691 | config.daemonmode = 1; 692 | break; 693 | case 'D': 694 | __debug = 1; 695 | break; 696 | case 'p': 697 | config.port = atoi(optarg); 698 | break; 699 | case 'a': 700 | config.addr = optarg; 701 | break; 702 | case 'P': 703 | config.log_path = optarg; 704 | break; 705 | case 't': 706 | if (optarg) 707 | config.tls_ctx_config.tls_port = atoi(optarg); 708 | else 709 | config.tls_ctx_config.tls_port = DEFAULT_TLS_PORT; 710 | break; 711 | case 'A': 712 | config.tls_ctx_config.tls_addr = optarg; 713 | break; 714 | case 'C': 715 | config.tls_ctx_config.ca_cert_file = optarg; 716 | break; 717 | case 'c': 718 | config.tls_ctx_config.cert_file = optarg; 719 | break; 720 | case 'k': 721 | config.tls_ctx_config.key_file = optarg; 722 | break; 723 | case 'H': 724 | hidecmdline = 1; 725 | break; 726 | case 'V': 727 | httpd_showversion(); 728 | case 'h': 729 | default: 730 | httpd_showhelp(); 731 | } 732 | } 733 | 734 | pagesize = sysconf(_SC_PAGESIZE); 735 | hertz = sysconf(_SC_CLK_TCK); 736 | uname(&utsname); 737 | 738 | if (config.log_path == NULL) { 739 | printf("%s: log path is nil\n", __func__); 740 | return -1; 741 | } 742 | 743 | if (rawlog_parse_all(config.log_path)) { 744 | printf("%s: rawlog parse failed\n", __func__); 745 | return -1; 746 | } 747 | 748 | log_debug("%s runs with log path(%s), port(%d)\n", argv[0], config.log_path, config.port); 749 | 750 | printf("%s runs with log path(%s), port(%d)\n", argv[0], config.log_path, config.port); 751 | conntype_initialize(); 752 | errno = httpd(config); 753 | 754 | return errno; 755 | } 756 | -------------------------------------------------------------------------------- /json.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 zhenwei pi 3 | * 4 | * Authors: 5 | * zhenwei pi 6 | * Fei Li 7 | * 8 | * This work is licensed under the terms of the GNU GPL, version 2 or later. 9 | * See the COPYING file in the top-level directory. 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "config.h" 27 | #include "atop.h" 28 | #include "json.h" 29 | #include "output.h" 30 | 31 | #define LEN_HP_SIZE 64 32 | #define LINE_BUF_SIZE 1024 33 | 34 | static void json_print_CPU(); 35 | static void json_print_cpu(); 36 | static void json_print_CPL(); 37 | static void json_print_GPU(); 38 | static void json_print_MEM(); 39 | static void json_print_SWP(); 40 | static void json_print_PAG(); 41 | static void json_print_PSI(); 42 | static void json_print_LVM(); 43 | static void json_print_MDD(); 44 | static void json_print_DSK(); 45 | static void json_print_NFM(); 46 | static void json_print_NFC(); 47 | static void json_print_NFS(); 48 | static void json_print_NET(); 49 | static void json_print_IFB(); 50 | static void json_print_NUM(); 51 | static void json_print_NUC(); 52 | static void json_print_LLC(); 53 | static void json_print_PRG(); 54 | static void json_print_PRC(); 55 | static void json_print_PRM(); 56 | static void json_print_PRD(); 57 | static void json_print_PRN(); 58 | static void json_print_PRE(); 59 | 60 | /* 61 | ** table with possible labels and the corresponding 62 | ** print-function for json style output 63 | */ 64 | struct labeldef { 65 | char *label; 66 | int valid; 67 | void (*prifunc)(int, char *, struct sstat *, struct tstat *, int); 68 | }; 69 | 70 | extern struct output defop; 71 | 72 | static int jsondef(char *pd, struct labeldef *labeldef, int numlabels) 73 | { 74 | int i; 75 | char *p, *ep = pd + strlen(pd); 76 | 77 | if (*pd == '-') { 78 | char *err = "json lables should be followed by label list\n"; 79 | output_samp(&defop, err, strlen(err)); 80 | return -EINVAL; 81 | } 82 | 83 | while (pd < ep) { 84 | /* 85 | ** exchange comma by null-byte 86 | */ 87 | if ((p = strchr(pd, ','))) 88 | *p = 0; 89 | else 90 | p = ep - 1; 91 | 92 | /* 93 | ** check if the next label exists 94 | */ 95 | for (i = 0; i < numlabels; i++) 96 | { 97 | if (!strcmp(labeldef[i].label, pd)) { 98 | labeldef[i].valid = 1; 99 | break; 100 | } 101 | } 102 | 103 | if (i == numlabels) 104 | { 105 | if (!strcmp("ALL", pd)) { 106 | for (i = 0; i < numlabels; i++) 107 | labeldef[i].valid = 1; 108 | break; 109 | } else { 110 | char err[64]; 111 | snprintf(err, sizeof(err), "json lables not supported: %s\n", pd); 112 | output_samp(&defop, err, strlen(err)); 113 | return -EINVAL; 114 | } 115 | } 116 | 117 | pd = p + 1; 118 | } 119 | 120 | return 0; 121 | } 122 | 123 | int jsonout(int flags, char *pd, time_t curtime, int numsecs, 124 | struct devtstat *devtstat, struct sstat *sstat, 125 | int nexit, unsigned int noverflow, char flag, connection *conn) 126 | { 127 | char header[256], general[256]; 128 | struct tstat *tmp = devtstat->taskall; 129 | int buflen = 0; 130 | int i, ret; 131 | 132 | struct labeldef labeldef[] = { 133 | { "CPU", 0, json_print_CPU }, 134 | { "cpu", 0, json_print_cpu }, 135 | { "CPL", 0, json_print_CPL }, 136 | { "GPU", 0, json_print_GPU }, 137 | { "MEM", 0, json_print_MEM }, 138 | { "SWP", 0, json_print_SWP }, 139 | { "PAG", 0, json_print_PAG }, 140 | { "PSI", 0, json_print_PSI }, 141 | { "LVM", 0, json_print_LVM }, 142 | { "MDD", 0, json_print_MDD }, 143 | { "DSK", 0, json_print_DSK }, 144 | { "NFM", 0, json_print_NFM }, 145 | { "NFC", 0, json_print_NFC }, 146 | { "NFS", 0, json_print_NFS }, 147 | { "NET", 0, json_print_NET }, 148 | { "IFB", 0, json_print_IFB }, 149 | { "NUM", 0, json_print_NUM }, 150 | { "NUC", 0, json_print_NUC }, 151 | { "LLC", 0, json_print_LLC }, 152 | 153 | { "PRG", 0, json_print_PRG }, 154 | { "PRC", 0, json_print_PRC }, 155 | { "PRM", 0, json_print_PRM }, 156 | { "PRD", 0, json_print_PRD }, 157 | { "PRN", 0, json_print_PRN }, 158 | { "PRE", 0, json_print_PRE }, 159 | }; 160 | 161 | int numlabels = sizeof(labeldef) / sizeof(struct labeldef); 162 | 163 | ret = jsondef(pd, labeldef, numlabels); 164 | if (ret) { 165 | output_samp_done(&defop, conn); 166 | return ret; 167 | } 168 | 169 | buflen = snprintf(general, sizeof(general), 170 | "{\"host\": \"%s\", " 171 | "\"timestamp\": %ld, " 172 | "\"elapsed\": %d", 173 | utsname.nodename, 174 | curtime, 175 | numsecs 176 | ); 177 | 178 | output_samp(&defop, general, buflen); 179 | 180 | /* Replace " with # in case json can not parse this out */ 181 | for (int k = 0; k < devtstat->ntaskall; k++, tmp++) { 182 | for (int j = 0; (j < sizeof(tmp->gen.name)) && tmp->gen.name[j]; j++) 183 | if ((tmp->gen.name[j] == '\"') || (tmp->gen.name[j] == '\\')) 184 | tmp->gen.name[j] = '#'; 185 | 186 | if (hidecmdline) { 187 | strcpy(tmp->gen.cmdline, "***"); 188 | continue; 189 | } 190 | 191 | for (int j = 0; (j < sizeof(tmp->gen.cmdline) && tmp->gen.cmdline[j]); j++) 192 | if ((tmp->gen.cmdline[j] == '\"') || (tmp->gen.cmdline[j] == '\\')) 193 | tmp->gen.cmdline[j] = '#'; 194 | } 195 | 196 | for (i = 0; i < numlabels; i++) { 197 | if (!labeldef[i].valid) 198 | continue; 199 | 200 | /* prepare generic columns */ 201 | snprintf(header, sizeof header, "\"%s\"", 202 | labeldef[i].label); 203 | /* call all print-functions */ 204 | (labeldef[i].prifunc)(flags, header, sstat, devtstat->taskall, devtstat->ntaskall); 205 | } 206 | 207 | output_samp(&defop, "}\n", 2); 208 | output_samp_done(&defop, conn); 209 | 210 | return 0; 211 | } 212 | 213 | /* 214 | ** print functions for system-level statistics 215 | */ 216 | static void 217 | json_calc_freqscale(count_t maxfreq, count_t cnt, count_t ticks, 218 | count_t *freq, int *freqperc) 219 | { 220 | // if ticks != 0,do full calcs 221 | if (maxfreq && ticks) { 222 | *freq = cnt / ticks; 223 | *freqperc = 100* *freq / maxfreq; 224 | } else if (maxfreq) { // max frequency is known so % can be calculated 225 | *freq = cnt; 226 | *freqperc = 100*cnt/maxfreq; 227 | } else if (cnt) { // no max known, set % to 100 228 | *freq = cnt; 229 | *freqperc = 100; 230 | } else { // nothing is known: set freq to 0, % to 100 231 | *freq = 0; 232 | *freqperc = 100; 233 | } 234 | } 235 | 236 | static void json_print_CPU(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 237 | { 238 | count_t maxfreq = 0; 239 | count_t cnt = 0; 240 | count_t ticks = 0; 241 | count_t freq; 242 | int freqperc; 243 | int i; 244 | int buflen = 0; 245 | char buf[LINE_BUF_SIZE]; 246 | 247 | // calculate average clock frequency 248 | for (i = 0; i < ss->cpu.nrcpu; i++) { 249 | cnt += ss->cpu.cpu[i].freqcnt.cnt; 250 | ticks += ss->cpu.cpu[i].freqcnt.ticks; 251 | } 252 | maxfreq = ss->cpu.cpu[0].freqcnt.maxfreq; 253 | json_calc_freqscale(maxfreq, cnt, ticks, &freq, &freqperc); 254 | 255 | if (ss->cpu.all.instr == 1) { 256 | ss->cpu.all.instr = 0; 257 | ss->cpu.all.cycle = 0; 258 | } 259 | 260 | buflen = snprintf(buf, sizeof(buf), ", %s: {" 261 | "\"hertz\": %u, " 262 | "\"nrcpu\": %lld, " 263 | "\"stime\": %lld, " 264 | "\"utime\": %lld, " 265 | "\"ntime\": %lld, " 266 | "\"itime\": %lld, " 267 | "\"wtime\": %lld, " 268 | "\"Itime\": %lld, " 269 | "\"Stime\": %lld, " 270 | "\"steal\": %lld, " 271 | "\"guest\": %lld, " 272 | "\"freq\": %lld, " 273 | "\"freqperc\": %d, " 274 | "\"instr\": %lld, " 275 | "\"cycle\": %lld}", 276 | hp, 277 | hertz, 278 | ss->cpu.nrcpu, 279 | ss->cpu.all.stime, 280 | ss->cpu.all.utime, 281 | ss->cpu.all.ntime, 282 | ss->cpu.all.itime, 283 | ss->cpu.all.wtime, 284 | ss->cpu.all.Itime, 285 | ss->cpu.all.Stime, 286 | ss->cpu.all.steal, 287 | ss->cpu.all.guest, 288 | freq, 289 | freqperc, 290 | ss->cpu.all.instr, 291 | ss->cpu.all.cycle 292 | ); 293 | 294 | output_samp(&defop, buf, buflen); 295 | } 296 | 297 | static void json_print_cpu(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 298 | { 299 | int i; 300 | count_t maxfreq = 0; 301 | count_t cnt = 0; 302 | count_t ticks = 0; 303 | count_t freq; 304 | int freqperc; 305 | int buflen = 0; 306 | char buf[LINE_BUF_SIZE]; 307 | 308 | char br[LEN_HP_SIZE]; 309 | buflen = sprintf(br, ", %s: [", hp); 310 | output_samp(&defop, br, buflen); 311 | 312 | for (i = 0; i < ss->cpu.nrcpu; i++) { 313 | if (i > 0) { 314 | output_samp(&defop, ", ", 2); 315 | } 316 | cnt = ss->cpu.cpu[i].freqcnt.cnt; 317 | ticks = ss->cpu.cpu[i].freqcnt.ticks; 318 | maxfreq= ss->cpu.cpu[0].freqcnt.maxfreq; 319 | 320 | json_calc_freqscale(maxfreq, cnt, ticks, &freq, &freqperc); 321 | 322 | buflen = snprintf(buf, sizeof(buf), "{\"cpuid\": %d, " 323 | "\"stime\": %lld, " 324 | "\"utime\": %lld, " 325 | "\"ntime\": %lld, " 326 | "\"itime\": %lld, " 327 | "\"wtime\": %lld, " 328 | "\"Itime\": %lld, " 329 | "\"Stime\": %lld, " 330 | "\"steal\": %lld, " 331 | "\"guest\": %lld, " 332 | "\"freq\": %lld, " 333 | "\"freqperc\": %d, " 334 | "\"instr\": %lld, " 335 | "\"cycle\": %lld}", 336 | i, 337 | ss->cpu.cpu[i].stime, 338 | ss->cpu.cpu[i].utime, 339 | ss->cpu.cpu[i].ntime, 340 | ss->cpu.cpu[i].itime, 341 | ss->cpu.cpu[i].wtime, 342 | ss->cpu.cpu[i].Itime, 343 | ss->cpu.cpu[i].Stime, 344 | ss->cpu.cpu[i].steal, 345 | ss->cpu.cpu[i].guest, 346 | freq, 347 | freqperc, 348 | ss->cpu.cpu[i].instr, 349 | ss->cpu.cpu[i].cycle 350 | ); 351 | output_samp(&defop, buf, buflen); 352 | } 353 | 354 | output_samp(&defop, "]", 1); 355 | } 356 | 357 | static void json_print_CPL(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 358 | { 359 | int buflen = 0; 360 | char buf[LINE_BUF_SIZE]; 361 | 362 | buflen = snprintf(buf, sizeof(buf), ", %s: {" 363 | "\"lavg1\": %.2f, " 364 | "\"lavg5\": %.2f, " 365 | "\"lavg15\": %.2f, " 366 | "\"csw\": %lld, " 367 | "\"devint\": %lld}", 368 | hp, 369 | ss->cpu.lavg1, 370 | ss->cpu.lavg5, 371 | ss->cpu.lavg15, 372 | ss->cpu.csw, 373 | ss->cpu.devint); 374 | 375 | output_samp(&defop, buf, buflen); 376 | } 377 | 378 | static void json_print_GPU(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 379 | { 380 | int i; 381 | int buflen = 0; 382 | char buf[LINE_BUF_SIZE]; 383 | 384 | char br[LEN_HP_SIZE]; 385 | buflen = sprintf(br, ", %s: [", hp); 386 | output_samp(&defop, br, buflen); 387 | 388 | for (i = 0; i < ss->gpu.nrgpus; i++) { 389 | if (i > 0) { 390 | output_samp(&defop, ", ", 2); 391 | } 392 | buflen = snprintf(buf, sizeof(buf), "{\"gpuid\": %d, " 393 | "\"busid\": \"%.19s\", " 394 | "\"type\": \"%.19s\", " 395 | "\"gpupercnow\": %d, " 396 | "\"mempercnow\": %d, " 397 | "\"memtotnow\": %lld, " 398 | "\"memusenow\": %lld, " 399 | "\"samples\": %lld, " 400 | "\"gpuperccum\": %lld, " 401 | "\"memperccum\": %lld, " 402 | "\"memusecum\": %lld}", 403 | i, 404 | ss->gpu.gpu[i].busid, 405 | ss->gpu.gpu[i].type, 406 | ss->gpu.gpu[i].gpupercnow, 407 | ss->gpu.gpu[i].mempercnow, 408 | ss->gpu.gpu[i].memtotnow, 409 | ss->gpu.gpu[i].memusenow, 410 | ss->gpu.gpu[i].samples, 411 | ss->gpu.gpu[i].gpuperccum, 412 | ss->gpu.gpu[i].memperccum, 413 | ss->gpu.gpu[i].memusecum); 414 | output_samp(&defop, buf, buflen); 415 | } 416 | 417 | output_samp(&defop, "]", 1); 418 | } 419 | 420 | static void json_print_MEM(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 421 | { 422 | int buflen = 0; 423 | char buf[LINE_BUF_SIZE]; 424 | 425 | buflen = snprintf(buf, sizeof(buf), ", %s: {" 426 | "\"physmem\": %lld, " 427 | "\"freemem\": %lld, " 428 | "\"cachemem\": %lld, " 429 | "\"buffermem\": %lld, " 430 | "\"slabmem\": %lld, " 431 | "\"cachedrt\": %lld, " 432 | "\"slabreclaim\": %lld, " 433 | "\"vmwballoon\": %lld, " 434 | "\"shmem\": %lld, " 435 | "\"shmrss\": %lld, " 436 | "\"shmswp\": %lld, " 437 | "\"pagetables\": %lld, " 438 | "\"hugepagesz\": %lld, " 439 | "\"tothugepage\": %lld, " 440 | "\"freehugepage\": %lld, " 441 | "\"tcpsk\": %lld, " 442 | "\"udpsk\": %lld}", 443 | hp, 444 | ss->mem.physmem * pagesize, 445 | ss->mem.freemem * pagesize, 446 | ss->mem.cachemem * pagesize, 447 | ss->mem.buffermem * pagesize, 448 | ss->mem.slabmem * pagesize, 449 | ss->mem.cachedrt * pagesize, 450 | ss->mem.slabreclaim * pagesize, 451 | ss->mem.vmwballoon * pagesize, 452 | ss->mem.shmem * pagesize, 453 | ss->mem.shmrss * pagesize, 454 | ss->mem.shmswp * pagesize, 455 | ss->mem.pagetables * pagesize, 456 | ss->mem.hugepagesz, 457 | ss->mem.tothugepage, 458 | ss->mem.freehugepage, 459 | ss->mem.tcpsock * pagesize, 460 | ss->mem.udpsock * pagesize); 461 | 462 | output_samp(&defop, buf, buflen); 463 | } 464 | 465 | static void json_print_SWP(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 466 | { 467 | int buflen = 0; 468 | char buf[LINE_BUF_SIZE]; 469 | 470 | buflen = snprintf(buf, sizeof(buf), ", %s: {" 471 | "\"totswap\": %lld, " 472 | "\"freeswap\": %lld, " 473 | "\"swcac\": %lld, " 474 | "\"committed\": %lld, " 475 | "\"commitlim\": %lld}", 476 | hp, 477 | ss->mem.totswap * pagesize, 478 | ss->mem.freeswap * pagesize, 479 | ss->mem.swapcached * pagesize, 480 | ss->mem.committed * pagesize, 481 | ss->mem.commitlim * pagesize); 482 | 483 | output_samp(&defop, buf, buflen); 484 | } 485 | 486 | static void json_print_PAG(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 487 | { 488 | int buflen = 0; 489 | char buf[LINE_BUF_SIZE]; 490 | 491 | buflen = snprintf(buf, sizeof(buf), ", %s: {" 492 | "\"stall\": %lld, " 493 | "\"compacts\": %lld, " 494 | "\"numamigs\": %lld, " 495 | "\"migrates\": %lld, " 496 | "\"pgscans\": %lld, " 497 | "\"pgsteal\": %lld," 498 | "\"allocstall\": %lld, " 499 | "\"pgins\": %lld, " 500 | "\"pgouts\": %lld, " 501 | "\"swins\": %lld, " 502 | "\"swouts\": %lld, " 503 | "\"oomkills\": %lld}", 504 | hp, 505 | ss->mem.allocstall, 506 | ss->mem.compactstall, 507 | ss->mem.numamigrate, 508 | ss->mem.pgmigrate, 509 | ss->mem.pgscans, 510 | ss->mem.pgsteal, 511 | ss->mem.allocstall, 512 | ss->mem.pgins, 513 | ss->mem.pgouts, 514 | ss->mem.swins, 515 | ss->mem.swouts, 516 | ss->mem.oomkills); 517 | 518 | output_samp(&defop, buf, buflen); 519 | } 520 | 521 | static void json_print_PSI(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 522 | { 523 | if ( !(ss->psi.present) ) 524 | return; 525 | 526 | int buflen = 0; 527 | char buf[LINE_BUF_SIZE]; 528 | 529 | buflen = snprintf(buf, sizeof(buf), ", %s: {" 530 | "\"psi\": \"%c\", " 531 | "\"cs10\": %.1f, " 532 | "\"cs60\": %.1f, " 533 | "\"cs300\": %.1f, " 534 | "\"cstot\": %llu, " 535 | "\"ms10\": %.1f, " 536 | "\"ms60\": %.1f, " 537 | "\"ms300\": %.1f, " 538 | "\"mstot\": %llu, " 539 | "\"mf10\": %.1f, " 540 | "\"mf60\": %.1f, " 541 | "\"mf300\": %.1f, " 542 | "\"mftot\": %llu, " 543 | "\"ios10\": %.1f, " 544 | "\"ios60\": %.1f, " 545 | "\"ios300\": %.1f, " 546 | "\"iostot\": %llu, " 547 | "\"iof10\": %.1f, " 548 | "\"iof60\": %.1f, " 549 | "\"iof300\": %.1f, " 550 | "\"ioftot\": %llu}", 551 | hp, ss->psi.present ? 'y' : 'n', 552 | ss->psi.cpusome.avg10, ss->psi.cpusome.avg60, 553 | ss->psi.cpusome.avg300, ss->psi.cpusome.total, 554 | ss->psi.memsome.avg10, ss->psi.memsome.avg60, 555 | ss->psi.memsome.avg300, ss->psi.memsome.total, 556 | ss->psi.memfull.avg10, ss->psi.memfull.avg60, 557 | ss->psi.memfull.avg300, ss->psi.memfull.total, 558 | ss->psi.iosome.avg10, ss->psi.iosome.avg60, 559 | ss->psi.iosome.avg300, ss->psi.iosome.total, 560 | ss->psi.iofull.avg10, ss->psi.iofull.avg60, 561 | ss->psi.iofull.avg300, ss->psi.iofull.total); 562 | 563 | output_samp(&defop, buf, buflen); 564 | } 565 | 566 | static void json_print_LVM(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 567 | { 568 | int i; 569 | int buflen = 0; 570 | char buf[LINE_BUF_SIZE]; 571 | 572 | char br[LEN_HP_SIZE]; 573 | buflen = sprintf(br, ", %s: [", hp); 574 | output_samp(&defop, br, buflen); 575 | 576 | for (i = 0; ss->dsk.lvm[i].name[0]; i++) { 577 | if (i > 0) { 578 | output_samp(&defop, ", ", 2); 579 | } 580 | buflen = snprintf(buf, sizeof(buf), "{\"lvmname\": \"%.19s\", " 581 | "\"io_ms\": %lld, " 582 | "\"nread\": %lld, " 583 | "\"ndiscrd\": %lld, " 584 | "\"nrsect\": %lld, " 585 | "\"nwrite\": %lld, " 586 | "\"nwsect\": %lld, " 587 | "\"avque\": %lld, " 588 | "\"inflight\": %lld}", 589 | ss->dsk.lvm[i].name, 590 | ss->dsk.lvm[i].io_ms, 591 | ss->dsk.lvm[i].nread, 592 | ss->dsk.lvm[i].ndisc, 593 | ss->dsk.lvm[i].nrsect, 594 | ss->dsk.lvm[i].nwrite, 595 | ss->dsk.lvm[i].nwsect, 596 | ss->dsk.lvm[i].avque, 597 | ss->dsk.lvm[i].inflight); 598 | output_samp(&defop, buf, buflen); 599 | } 600 | 601 | output_samp(&defop, "]", 1); 602 | } 603 | 604 | static void json_print_MDD(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 605 | { 606 | int i; 607 | int buflen = 0; 608 | char buf[LINE_BUF_SIZE]; 609 | 610 | char br[LEN_HP_SIZE]; 611 | buflen = sprintf(br, ", %s: [", hp); 612 | output_samp(&defop, br, buflen); 613 | 614 | for (i = 0; ss->dsk.mdd[i].name[0]; i++) { 615 | if (i > 0) { 616 | output_samp(&defop, ", ", 2); 617 | } 618 | buflen = snprintf(buf, sizeof(buf), "{\"mddname\": \"%.19s\", " 619 | "\"io_ms\": %lld, " 620 | "\"nread\": %lld, " 621 | "\"nrsect\": %lld, " 622 | "\"nwrite\": %lld, " 623 | "\"nwsect\": %lld, " 624 | "\"avque\": %lld, " 625 | "\"inflight\": %lld}", 626 | ss->dsk.mdd[i].name, 627 | ss->dsk.mdd[i].io_ms, 628 | ss->dsk.mdd[i].nread, 629 | ss->dsk.mdd[i].nrsect, 630 | ss->dsk.mdd[i].nwrite, 631 | ss->dsk.mdd[i].nwsect, 632 | ss->dsk.mdd[i].avque, 633 | ss->dsk.mdd[i].inflight); 634 | output_samp(&defop, buf, buflen); 635 | } 636 | 637 | output_samp(&defop, "]", 1); 638 | } 639 | 640 | static void json_print_DSK(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 641 | { 642 | int i; 643 | int buflen = 0; 644 | char buf[LINE_BUF_SIZE]; 645 | 646 | char br[LEN_HP_SIZE]; 647 | buflen = sprintf(br, ", %s: [", hp); 648 | output_samp(&defop, br, buflen); 649 | 650 | for (i = 0; ss->dsk.dsk[i].name[0]; i++) { 651 | if (i > 0) { 652 | output_samp(&defop, ", ", 2); 653 | } 654 | buflen = snprintf(buf, sizeof(buf), "{\"dskname\": \"%.19s\", " 655 | "\"io_ms\": %lld, " 656 | "\"nread\": %lld, " 657 | "\"nrsect\": %lld, " 658 | "\"ndiscrd\": %lld, " 659 | "\"nwrite\": %lld, " 660 | "\"nwsect\": %lld, " 661 | "\"avque\": %lld, " 662 | "\"inflight\": %lld}", 663 | ss->dsk.dsk[i].name, 664 | ss->dsk.dsk[i].io_ms, 665 | ss->dsk.dsk[i].nread, 666 | ss->dsk.dsk[i].nrsect, 667 | ss->dsk.dsk[i].ndisc, 668 | ss->dsk.dsk[i].nwrite, 669 | ss->dsk.dsk[i].nwsect, 670 | ss->dsk.dsk[i].avque, 671 | ss->dsk.dsk[i].inflight); 672 | output_samp(&defop, buf, buflen); 673 | } 674 | 675 | output_samp(&defop, "]", 1); 676 | } 677 | 678 | static void json_print_NFM(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 679 | { 680 | int i; 681 | int buflen = 0; 682 | char buf[LINE_BUF_SIZE]; 683 | 684 | char br[LEN_HP_SIZE]; 685 | buflen = sprintf(br, ", %s: [", hp); 686 | output_samp(&defop, br, buflen); 687 | 688 | for (i = 0; i < ss->nfs.nfsmounts.nrmounts; i++) { 689 | if (i > 0) { 690 | output_samp(&defop, ", ", 2); 691 | } 692 | buflen = snprintf(buf, sizeof(buf), "{\"mountdev\": \"%.19s\", " 693 | "\"bytestotread\": %lld, " 694 | "\"bytestotwrite\": %lld, " 695 | "\"bytesread\": %lld, " 696 | "\"byteswrite\": %lld, " 697 | "\"bytesdread\": %lld, " 698 | "\"bytesdwrite\": %lld, " 699 | "\"pagesmread\": %lld, " 700 | "\"pagesmwrite\": %lld}", 701 | ss->nfs.nfsmounts.nfsmnt[i].mountdev, 702 | ss->nfs.nfsmounts.nfsmnt[i].bytestotread, 703 | ss->nfs.nfsmounts.nfsmnt[i].bytestotwrite, 704 | ss->nfs.nfsmounts.nfsmnt[i].bytesread, 705 | ss->nfs.nfsmounts.nfsmnt[i].byteswrite, 706 | ss->nfs.nfsmounts.nfsmnt[i].bytesdread, 707 | ss->nfs.nfsmounts.nfsmnt[i].bytesdwrite, 708 | ss->nfs.nfsmounts.nfsmnt[i].pagesmread * pagesize, 709 | ss->nfs.nfsmounts.nfsmnt[i].pagesmwrite * pagesize); 710 | output_samp(&defop, buf, buflen); 711 | } 712 | 713 | output_samp(&defop, "]", 1); 714 | } 715 | 716 | static void json_print_NFC(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 717 | { 718 | int buflen = 0; 719 | char buf[LINE_BUF_SIZE]; 720 | 721 | buflen = snprintf(buf, sizeof(buf), ", %s: {" 722 | "\"rpccnt\": %lld, " 723 | "\"rpcread\": %lld, " 724 | "\"rpcwrite\": %lld, " 725 | "\"rpcretrans\": %lld, " 726 | "\"rpcautrefresh\": %lld}", 727 | hp, 728 | ss->nfs.client.rpccnt, 729 | ss->nfs.client.rpcread, 730 | ss->nfs.client.rpcwrite, 731 | ss->nfs.client.rpcretrans, 732 | ss->nfs.client.rpcautrefresh); 733 | 734 | output_samp(&defop, buf, buflen); 735 | } 736 | 737 | static void json_print_NFS(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 738 | { 739 | int buflen = 0; 740 | char buf[LINE_BUF_SIZE]; 741 | 742 | buflen = snprintf(buf, sizeof(buf), ", %s: {" 743 | "\"rpccnt\": %lld, " 744 | "\"rpcread\": %lld, " 745 | "\"rpcwrite\": %lld, " 746 | "\"nrbytes\": %lld, " 747 | "\"nwbytes\": %lld, " 748 | "\"rpcbadfmt\": %lld, " 749 | "\"rpcbadaut\": %lld, " 750 | "\"rpcbadcln\": %lld, " 751 | "\"netcnt\": %lld, " 752 | "\"nettcpcnt\": %lld, " 753 | "\"netudpcnt\": %lld, " 754 | "\"nettcpcon\": %lld, " 755 | "\"rchits\": %lld, " 756 | "\"rcmiss\": %lld, " 757 | "\"rcnocache\": %lld}", 758 | hp, 759 | ss->nfs.server.rpccnt, 760 | ss->nfs.server.rpcread, 761 | ss->nfs.server.rpcwrite, 762 | ss->nfs.server.nrbytes, 763 | ss->nfs.server.nwbytes, 764 | ss->nfs.server.rpcbadfmt, 765 | ss->nfs.server.rpcbadaut, 766 | ss->nfs.server.rpcbadcln, 767 | ss->nfs.server.netcnt, 768 | ss->nfs.server.nettcpcnt, 769 | ss->nfs.server.netudpcnt, 770 | ss->nfs.server.nettcpcon, 771 | ss->nfs.server.rchits, 772 | ss->nfs.server.rcmiss, 773 | ss->nfs.server.rcnoca); 774 | 775 | output_samp(&defop, buf, buflen); 776 | } 777 | 778 | static void json_print_NET(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 779 | { 780 | int i; 781 | int buflen = 0; 782 | char buf[LINE_BUF_SIZE]; 783 | 784 | buflen = snprintf(buf, sizeof(buf), ", \"NET_GENERAL\": {" 785 | "\"rpacketsTCP\": %lld, " 786 | "\"spacketsTCP\": %lld, " 787 | "\"inerrTCP\": %lld, " 788 | "\"oresetTCP\": %lld, " 789 | "\"activeOpensTCP\": %lld, " 790 | "\"passiveOpensTCP\": %lld, " 791 | "\"retransSegsTCP\": %lld, " 792 | "\"noportUDP\": %lld, " 793 | "\"inerrUDP\": %lld, " 794 | "\"rpacketsUDP\": %lld, " 795 | "\"spacketsUDP\": %lld, " 796 | "\"rpacketsIP\": %lld, " 797 | "\"spacketsIP\": %lld, " 798 | "\"dpacketsIP\": %lld, " 799 | "\"fpacketsIP\": %lld, " 800 | "\"icmpi\" : %lld, " 801 | "\"icmpo\" : %lld}", 802 | ss->net.tcp.InSegs, 803 | ss->net.tcp.OutSegs, 804 | ss->net.tcp.InErrs, 805 | ss->net.tcp.OutRsts, 806 | ss->net.tcp.ActiveOpens, 807 | ss->net.tcp.PassiveOpens, 808 | ss->net.tcp.RetransSegs, 809 | ss->net.udpv4.NoPorts, 810 | ss->net.udpv4.InErrors, 811 | ss->net.udpv4.InDatagrams + 812 | ss->net.udpv6.Udp6InDatagrams, 813 | ss->net.udpv4.OutDatagrams + 814 | ss->net.udpv6.Udp6OutDatagrams, 815 | ss->net.ipv4.InReceives + 816 | ss->net.ipv6.Ip6InReceives, 817 | ss->net.ipv4.OutRequests + 818 | ss->net.ipv6.Ip6OutRequests, 819 | ss->net.ipv4.InDelivers + 820 | ss->net.ipv6.Ip6InDelivers, 821 | ss->net.ipv4.ForwDatagrams + 822 | ss->net.ipv6.Ip6OutForwDatagrams, 823 | ss->net.icmpv4.InMsgs + 824 | ss->net.icmpv6.Icmp6InMsgs, 825 | ss->net.icmpv4.OutMsgs + 826 | ss->net.icmpv6.Icmp6OutMsgs); 827 | output_samp(&defop, buf, buflen); 828 | 829 | char br[LEN_HP_SIZE]; 830 | buflen = sprintf(br, ", %s: [", hp); 831 | output_samp(&defop, br, buflen); 832 | 833 | for (i = 0; ss->intf.intf[i].name[0]; i++) { 834 | if (i > 0) { 835 | output_samp(&defop, ", ", 2); 836 | } 837 | buflen = snprintf(buf, sizeof(buf), "{\"name\": \"%.19s\", " 838 | "\"rpack\": %lld, " 839 | "\"rbyte\": %lld, " 840 | "\"rerrs\": %lld, " 841 | "\"rdrops\": %lld, " 842 | "\"spack\": %lld, " 843 | "\"sbyte\": %lld, " 844 | "\"serrs\": %lld, " 845 | "\"sdrops\": %lld, " 846 | "\"speed\": \"%ld\", " 847 | "\"coll\": %lld, " 848 | "\"multi\": %lld, " 849 | "\"duplex\": %d}", 850 | ss->intf.intf[i].name, 851 | ss->intf.intf[i].rpack, 852 | ss->intf.intf[i].rbyte, 853 | ss->intf.intf[i].rerrs, 854 | ss->intf.intf[i].rdrop, 855 | ss->intf.intf[i].spack, 856 | ss->intf.intf[i].sbyte, 857 | ss->intf.intf[i].serrs, 858 | ss->intf.intf[i].sdrop, 859 | ss->intf.intf[i].speed, 860 | ss->intf.intf[i].scollis, 861 | ss->intf.intf[i].rmultic, 862 | ss->intf.intf[i].duplex); 863 | output_samp(&defop, buf, buflen); 864 | } 865 | 866 | output_samp(&defop, "]", 1); 867 | } 868 | 869 | static void json_print_IFB(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 870 | { 871 | int i; 872 | int buflen = 0; 873 | char buf[LINE_BUF_SIZE]; 874 | 875 | char br[LEN_HP_SIZE]; 876 | buflen = sprintf(br, ", %s: [", hp); 877 | output_samp(&defop, br, buflen); 878 | 879 | for (i = 0; i < ss->ifb.nrports; i++) { 880 | if (i > 0) { 881 | output_samp(&defop, ", ", 2); 882 | } 883 | buflen = snprintf(buf, sizeof(buf), "{\"ibname\": \"%.19s\", " 884 | "\"portnr\": \"%hd\", " 885 | "\"lanes\": \"%hd\", " 886 | "\"maxrate\": %lld, " 887 | "\"rcvb\": %lld, " 888 | "\"sndb\": %lld, " 889 | "\"rcvp\": %lld, " 890 | "\"sndp\": %lld}", 891 | ss->ifb.ifb[i].ibname, 892 | ss->ifb.ifb[i].portnr, 893 | ss->ifb.ifb[i].lanes, 894 | ss->ifb.ifb[i].rate, 895 | ss->ifb.ifb[i].rcvb, 896 | ss->ifb.ifb[i].sndb, 897 | ss->ifb.ifb[i].rcvp, 898 | ss->ifb.ifb[i].sndp); 899 | output_samp(&defop, buf, buflen); 900 | } 901 | 902 | output_samp(&defop, "]", 1); 903 | } 904 | 905 | static void json_print_NUM(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 906 | { 907 | int i; 908 | int buflen = 0; 909 | char buf[LINE_BUF_SIZE]; 910 | 911 | char br[LEN_HP_SIZE]; 912 | buflen = sprintf(br, ", %s: [", hp); 913 | output_samp(&defop, br, buflen); 914 | 915 | for (i = 0; i < ss->memnuma.nrnuma; i++) { 916 | if (i > 0) { 917 | output_samp(&defop, ", ", 2); 918 | } 919 | buflen = snprintf(buf, sizeof(buf), "{\"frag\": \"%f\", " 920 | "\"totmem\": %lld, " 921 | "\"freemem\": %lld, " 922 | "\"active\": %lld, " 923 | "\"inactive\": %lld, " 924 | "\"filepage\": %lld, " 925 | "\"dirtymem\": %lld, " 926 | "\"slabmem\": %lld, " 927 | "\"slabreclaim\": %lld, " 928 | "\"shmem\": %lld, " 929 | "\"tothp\": %lld}", 930 | ss->memnuma.numa[i].frag * 100.0, 931 | ss->memnuma.numa[i].totmem * pagesize, 932 | ss->memnuma.numa[i].freemem * pagesize, 933 | ss->memnuma.numa[i].active * pagesize, 934 | ss->memnuma.numa[i].inactive * pagesize, 935 | ss->memnuma.numa[i].filepage * pagesize, 936 | ss->memnuma.numa[i].dirtymem * pagesize, 937 | ss->memnuma.numa[i].slabmem * pagesize, 938 | ss->memnuma.numa[i].slabreclaim * pagesize, 939 | ss->memnuma.numa[i].shmem * pagesize, 940 | ss->memnuma.numa[i].tothp * ss->mem.hugepagesz); 941 | output_samp(&defop, buf, buflen); 942 | } 943 | 944 | output_samp(&defop, "]", 1); 945 | } 946 | 947 | static void json_print_NUC(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 948 | { 949 | int i; 950 | int buflen = 0; 951 | char buf[LINE_BUF_SIZE]; 952 | 953 | char br[LEN_HP_SIZE]; 954 | buflen = sprintf(br, ", %s: [", hp); 955 | output_samp(&defop, br, buflen); 956 | 957 | for (i = 0; i < ss->cpunuma.nrnuma; i++) { 958 | if (i > 0) { 959 | output_samp(&defop, ", ", 2); 960 | } 961 | buflen = snprintf(buf, sizeof(buf), "{\"numanr\": \"%d\", " 962 | "\"nrcpu\": %lld, " 963 | "\"stime\": %lld, " 964 | "\"utime\": %lld, " 965 | "\"ntime\": %lld, " 966 | "\"itime\": %lld, " 967 | "\"wtime\": %lld, " 968 | "\"Itime\": %lld, " 969 | "\"Stime\": %lld, " 970 | "\"steal\": %lld, " 971 | "\"guest\": %lld}", 972 | ss->cpunuma.numa[i].numanr, 973 | ss->cpunuma.numa[i].nrcpu, 974 | ss->cpunuma.numa[i].stime, 975 | ss->cpunuma.numa[i].utime, 976 | ss->cpunuma.numa[i].ntime, 977 | ss->cpunuma.numa[i].itime, 978 | ss->cpunuma.numa[i].wtime, 979 | ss->cpunuma.numa[i].Itime, 980 | ss->cpunuma.numa[i].Stime, 981 | ss->cpunuma.numa[i].steal, 982 | ss->cpunuma.numa[i].guest); 983 | output_samp(&defop, buf, buflen); 984 | } 985 | 986 | output_samp(&defop, "]", 1); 987 | } 988 | 989 | static void json_print_LLC(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 990 | { 991 | int i; 992 | int buflen = 0; 993 | char buf[LINE_BUF_SIZE]; 994 | 995 | char br[LEN_HP_SIZE]; 996 | buflen = sprintf(br, ", %s: [", hp); 997 | output_samp(&defop, br, buflen); 998 | 999 | for (i = 0; i < ss->llc.nrllcs; i++) { 1000 | if (i > 0) { 1001 | output_samp(&defop, ", ", 2); 1002 | } 1003 | buflen = snprintf(buf, sizeof(buf), "{\"LLC\": \"%3d\", " 1004 | "\"occupancy\": \"%3.1f\", " 1005 | "\"mbm_total\": \"%lld\", " 1006 | "\"mbm_local\": %lld}", 1007 | ss->llc.perllc[i].id, 1008 | ss->llc.perllc[i].occupancy * 100, 1009 | ss->llc.perllc[i].mbm_total, 1010 | ss->llc.perllc[i].mbm_local); 1011 | output_samp(&defop, buf, buflen); 1012 | } 1013 | 1014 | output_samp(&defop, "]", 1); 1015 | } 1016 | 1017 | /* 1018 | ** print functions for process-level statistics 1019 | */ 1020 | static void json_print_PRG(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 1021 | { 1022 | int i, exitcode; 1023 | int buflen = 0; 1024 | char buf[LINE_BUF_SIZE]; 1025 | char br[LEN_HP_SIZE]; 1026 | buflen = sprintf(br, ", %s: [", hp); 1027 | output_samp(&defop, br, buflen); 1028 | 1029 | static char st[3]; 1030 | 1031 | for (i = 0; i < nact; i++, ps++) { 1032 | /* For one thread whose pid==tgid and isproc=n, it has the same 1033 | value with pid==tgid and isproc=y, thus filter it out. */ 1034 | if (ps->gen.tgid == ps->gen.pid && !ps->gen.isproc) 1035 | continue; 1036 | 1037 | if (ps->gen.excode & ~(INT_MAX)) 1038 | st[0]='N'; 1039 | else 1040 | st[0]='-'; 1041 | 1042 | if (ps->gen.excode & 0xff) // killed by signal? 1043 | { 1044 | exitcode = (ps->gen.excode & 0x7f) + 256; 1045 | if (ps->gen.excode & 0x80) 1046 | st[1] = 'C'; 1047 | else 1048 | st[1] = 'S'; 1049 | } 1050 | else 1051 | { 1052 | exitcode = (ps->gen.excode >> 8) & 0xff; 1053 | st[1] = 'E'; 1054 | } 1055 | 1056 | if (i > 0) { 1057 | output_samp(&defop, ", ", 2); 1058 | } 1059 | 1060 | /* using getpwuid() & getpwuid to convert ruid & euid to string seems better, but the two functions take a long time */ 1061 | buflen = snprintf(buf, sizeof(buf), "{\"pid\": %d, " 1062 | "\"name\": \"(%.19s)\", " 1063 | "\"state\": \"%c\", " 1064 | "\"ruid\": %d, " 1065 | "\"rgid\": %d, " 1066 | "\"tgid\": %d, " 1067 | "\"nthr\": %d, " 1068 | "\"st\": \"%s\", " 1069 | "\"exitcode\": %d, " 1070 | "\"btime\": \"%ld\", " 1071 | "\"cmdline\": \"(%.130s)\", " 1072 | "\"ppid\": %d, " 1073 | "\"nthrrun\": %d, " 1074 | "\"nthrslpi\": %d, " 1075 | "\"nthrslpu\": %d, " 1076 | "\"euid\": %d, " 1077 | "\"egid\": %d, " 1078 | "\"elaps\": \"%ld\", " 1079 | "\"isproc\": %d, " 1080 | "\"cid\": \"%.19s\"}", 1081 | ps->gen.pid, 1082 | ps->gen.name, 1083 | ps->gen.state, 1084 | ps->gen.ruid, 1085 | ps->gen.rgid, 1086 | ps->gen.tgid, 1087 | ps->gen.nthr, 1088 | st, 1089 | exitcode, 1090 | ps->gen.btime, 1091 | ps->gen.cmdline, 1092 | ps->gen.ppid, 1093 | ps->gen.nthrrun, 1094 | ps->gen.nthrslpi, 1095 | ps->gen.nthrslpu, 1096 | ps->gen.euid, 1097 | ps->gen.egid, 1098 | ps->gen.elaps, 1099 | !!ps->gen.isproc, /* convert to boolean */ 1100 | ps->gen.container[0] ? ps->gen.container:"-"); 1101 | output_samp(&defop, buf, buflen); 1102 | } 1103 | 1104 | output_samp(&defop, "]", 1); 1105 | } 1106 | 1107 | static void json_print_PRC(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 1108 | { 1109 | int i; 1110 | int buflen = 0; 1111 | char buf[LINE_BUF_SIZE]; 1112 | 1113 | char br[LEN_HP_SIZE]; 1114 | buflen = sprintf(br, ", %s: [", hp); 1115 | output_samp(&defop, br, buflen); 1116 | 1117 | for (i = 0; i < nact; i++, ps++) { 1118 | if (ps->gen.tgid == ps->gen.pid && !ps->gen.isproc) 1119 | continue; 1120 | if (i > 0) { 1121 | output_samp(&defop, ", ", 2); 1122 | } 1123 | buflen = snprintf(buf, sizeof(buf), "{\"pid\": %d, " 1124 | "\"utime\": %lld, " 1125 | "\"stime\": %lld, " 1126 | "\"nice\": %d, " 1127 | "\"prio\": %d, " 1128 | "\"curcpu\": %d, " 1129 | "\"tgid\": %d, " 1130 | "\"isproc\": %d, " 1131 | "\"rundelay\": %lld, " 1132 | "\"blkdelay\": %lld, " 1133 | "\"sleepavg\": %d}", 1134 | ps->gen.pid, 1135 | ps->cpu.utime, 1136 | ps->cpu.stime, 1137 | ps->cpu.nice, 1138 | ps->cpu.prio, 1139 | ps->cpu.curcpu, 1140 | ps->gen.tgid, 1141 | !!ps->gen.isproc, 1142 | ps->cpu.rundelay/1000000, 1143 | ps->cpu.blkdelay*1000/hertz, 1144 | ps->cpu.sleepavg); 1145 | output_samp(&defop, buf, buflen); 1146 | } 1147 | 1148 | output_samp(&defop, "]", 1); 1149 | } 1150 | 1151 | static void json_print_PRM(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 1152 | { 1153 | int i; 1154 | int buflen = 0; 1155 | char buf[LINE_BUF_SIZE]; 1156 | 1157 | char br[LEN_HP_SIZE]; 1158 | buflen = sprintf(br, ", %s: [", hp); 1159 | output_samp(&defop, br, buflen); 1160 | 1161 | for (i = 0; i < nact; i++, ps++) { 1162 | if (ps->gen.tgid == ps->gen.pid && !ps->gen.isproc) 1163 | continue; 1164 | if (i > 0) { 1165 | output_samp(&defop, ", ", 2); 1166 | } 1167 | buflen = snprintf(buf, sizeof(buf), "{\"pid\": %d, " 1168 | "\"vmem\": %lld, " 1169 | "\"rmem\": %lld, " 1170 | "\"vexec\": %lld, " 1171 | "\"vgrow\": %lld, " 1172 | "\"rgrow\": %lld, " 1173 | "\"minflt\": %lld, " 1174 | "\"majflt\": %lld, " 1175 | "\"vlibs\": %lld, " 1176 | "\"vdata\": %lld, " 1177 | "\"vstack\": %lld, " 1178 | "\"vlock\": %lld, " 1179 | "\"vswap\": %lld, " 1180 | "\"pmem\": %lld}", 1181 | ps->gen.pid, 1182 | ps->mem.vmem, 1183 | ps->mem.rmem, 1184 | ps->mem.vexec, 1185 | ps->mem.vgrow, 1186 | ps->mem.rgrow, 1187 | ps->mem.minflt, 1188 | ps->mem.majflt, 1189 | ps->mem.vlibs, 1190 | ps->mem.vdata, 1191 | ps->mem.vstack, 1192 | ps->mem.vlock, 1193 | ps->mem.vswap, 1194 | ps->mem.pmem == (unsigned long long)-1LL ? 1195 | 0:ps->mem.pmem); 1196 | output_samp(&defop, buf, buflen); 1197 | } 1198 | 1199 | output_samp(&defop, "]", 1); 1200 | } 1201 | 1202 | static void json_print_PRD(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 1203 | { 1204 | int i; 1205 | int buflen = 0; 1206 | char buf[LINE_BUF_SIZE]; 1207 | 1208 | char br[LEN_HP_SIZE]; 1209 | buflen = sprintf(br, ", %s: [", hp); 1210 | output_samp(&defop, br, buflen); 1211 | 1212 | for (i = 0; i < nact; i++, ps++) { 1213 | if (ps->gen.tgid == ps->gen.pid && !ps->gen.isproc) 1214 | continue; 1215 | if (i > 0) { 1216 | output_samp(&defop, ", ", 2); 1217 | } 1218 | buflen = snprintf(buf, sizeof(buf), "{\"pid\": %d, " 1219 | "\"rio\": %lld, " 1220 | "\"rsz\": %lld, " 1221 | "\"wio\": %lld, " 1222 | "\"wsz\": %lld, " 1223 | "\"cwsz\": %lld}", 1224 | ps->gen.pid, 1225 | ps->dsk.rio, ps->dsk.rsz, 1226 | ps->dsk.wio, ps->dsk.wsz, ps->dsk.cwsz); 1227 | output_samp(&defop, buf, buflen); 1228 | } 1229 | 1230 | output_samp(&defop, "]", 1); 1231 | } 1232 | 1233 | static void json_print_PRN(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 1234 | { 1235 | if (!(flags & NETATOP)) 1236 | return; 1237 | 1238 | int i; 1239 | int buflen = 0; 1240 | char buf[LINE_BUF_SIZE]; 1241 | 1242 | char br[LEN_HP_SIZE]; 1243 | buflen = sprintf(br, ", %s: [", hp); 1244 | output_samp(&defop, br, buflen); 1245 | 1246 | for (i = 0; i < nact; i++, ps++) { 1247 | if (ps->gen.tgid == ps->gen.pid && !ps->gen.isproc) 1248 | continue; 1249 | if (i > 0) { 1250 | output_samp(&defop, ", ", 2); 1251 | } 1252 | buflen = snprintf(buf, sizeof(buf), "{\"pid\": %d, " 1253 | "\"tcpsnd\": \"%lld\", " 1254 | "\"tcpssz\": \"%lld\", " 1255 | "\"tcprcv\": \"%lld\", " 1256 | "\"tcprsz\": \"%lld\", " 1257 | "\"udpsnd\": \"%lld\", " 1258 | "\"udpssz\": \"%lld\", " 1259 | "\"udprcv\": \"%lld\", " 1260 | "\"udprsz\": \"%lld\"}", 1261 | ps->gen.pid, 1262 | ps->net.tcpsnd, ps->net.tcpssz, 1263 | ps->net.tcprcv, ps->net.tcprsz, 1264 | ps->net.udpsnd, ps->net.udpssz, 1265 | ps->net.udprcv, ps->net.udprsz); 1266 | output_samp(&defop, buf, buflen); 1267 | } 1268 | 1269 | output_samp(&defop, "]", 1); 1270 | } 1271 | 1272 | static void json_print_PRE(int flags, char *hp, struct sstat *ss, struct tstat *ps, int nact) 1273 | { 1274 | if (!(flags & GPUSTAT) ) 1275 | return; 1276 | 1277 | int i; 1278 | int buflen = 0; 1279 | char buf[LINE_BUF_SIZE]; 1280 | 1281 | char br[LEN_HP_SIZE]; 1282 | buflen = sprintf(br, ", %s: [", hp); 1283 | output_samp(&defop, br, buflen); 1284 | 1285 | for (i = 0; i < nact; i++, ps++) { 1286 | if (ps->gen.tgid == ps->gen.pid && !ps->gen.isproc) 1287 | continue; 1288 | if (i > 0) { 1289 | output_samp(&defop, ", ", 2); 1290 | } 1291 | buflen = snprintf(buf, sizeof(buf), "{\"pid\": %d, " 1292 | "\"gpustate\": \"%c\", " 1293 | "\"nrgpus\": %d, " 1294 | "\"gpulist\": \"%x\", " 1295 | "\"gpubusy\": %d, " 1296 | "\"membusy\": %d, " 1297 | "\"memnow\": %lld, " 1298 | "\"memcum\": %lld, " 1299 | "\"sample\": %lld}", 1300 | ps->gen.pid, 1301 | ps->gpu.state == '\0' ? 'N':ps->gpu.state, 1302 | ps->gpu.nrgpus, 1303 | ps->gpu.gpulist, 1304 | ps->gpu.gpubusy, 1305 | ps->gpu.membusy, 1306 | ps->gpu.memnow, 1307 | ps->gpu.memcum, 1308 | ps->gpu.sample); 1309 | output_samp(&defop, buf, buflen); 1310 | } 1311 | 1312 | output_samp(&defop, "]", 1); 1313 | } 1314 | --------------------------------------------------------------------------------