├── screen1.jpg ├── screen2.jpg ├── screen3.jpg ├── index1.dot ├── TODO.md ├── index.ts ├── LICENSE.md ├── README.md ├── slant-collectd.h ├── slant-upgrade.in.sh ├── slant-upgrade.8 ├── slant-collectd.8 ├── index.css ├── slant.kwbp ├── slant-cgi.c ├── slant-collectd-freebsd.c ├── slant-cgi.8 ├── Makefile ├── slant-dns.c ├── versions.xml ├── slant-json.c ├── slant.h ├── index.xml ├── tests.c ├── slant.1 ├── slant-collectd.c ├── slant-http.c ├── slant.c ├── slant-collectd-linux.c ├── slant-config.c └── slant-collectd-openbsd.c /screen1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristapsdz/slant/HEAD/screen1.jpg -------------------------------------------------------------------------------- /screen2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristapsdz/slant/HEAD/screen2.jpg -------------------------------------------------------------------------------- /screen3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristapsdz/slant/HEAD/screen3.jpg -------------------------------------------------------------------------------- /index1.dot: -------------------------------------------------------------------------------- 1 | digraph figure1 { 2 | rankdir="LR"; 3 | subgraph cluster0 { 4 | collector -> db; 5 | db -> cgi; 6 | db[shape=square, fontcolor="#888888"]; 7 | cgi[label="slant-cgi(8)"]; 8 | collector[label="slant-collectd(8)"]; 9 | } 10 | client -> cgi; 11 | client[label="slant(1)"]; 12 | } 13 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | - Support compression in slant-http.c. 3 | 4 | - Show more things: boottime, etc. 5 | 6 | - Add multi-row support for extra information. 7 | 8 | - Average last two time series instead of showing only the last. 9 | Just showing the last is confusing when a bucket restarts, as it will 10 | only have the last data sample to show. 11 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | /* vim: set filetype=javascript: */ 2 | 3 | namespace index 4 | { 5 | function clicked(event: Event): void 6 | { 7 | let target: HTMLElement|null = 8 | event.target; 9 | while (target !== null && 10 | ! target.classList.contains('popup')) 11 | target = target.parentNode; 12 | if (target !== null) 13 | target.classList.toggle('shown'); 14 | } 15 | 16 | export function init(): void 17 | { 18 | let list: HTMLCollectionOf; 19 | let i: number; 20 | list = document.getElementsByClassName('popup'); 21 | for (i = 0; i < list.length; i++) 22 | (list[i]).onclick = clicked; 23 | } 24 | }; 25 | 26 | document.addEventListener('DOMContentLoaded', index.init); 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Kristaps Dzonsons 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose 4 | with or without fee is hereby granted, provided that the above copyright notice 5 | and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 11 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 12 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 13 | THIS SOFTWARE. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Please do not make feature requests! I'll just close the issue. 2 | Please don't take it the wrong way---if you want something, I'm happy to 3 | work with your efforts to patch the existing system.** 4 | 5 | **Alternatively, if you wish to fund development for particular 6 | features, please contact me privately.** 7 | 8 | ## Synopsis 9 | 10 | *slant* is a remote system monitor. For the time being, it only works 11 | with OpenBSD hosts, though Linux is technically working (if still under 12 | development). 13 | 14 | The website has all the information you might need, and the manpages 15 | document the system itself. The GitHub site is for issues and pull 16 | requests, as the source code is versioned in a private 17 | [BSD.lv](https://bsd.lv) repository. 18 | 19 | If you'd like to help out, please e-mail me or just dive right in with a 20 | pull request. 21 | 22 | ## License 23 | 24 | All sources use the ISC (like OpenBSD) license. 25 | See the [LICENSE.md](LICENSE.md) file for details. 26 | -------------------------------------------------------------------------------- /slant-collectd.h: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2018 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #ifndef SLANT_COLLECT_H 18 | #define SLANT_COLLECT_H 19 | 20 | /* 21 | * Configuration of things we're going to look for. 22 | */ 23 | struct syscfg { 24 | char **discs; /* disc devices (e.g., sd0) */ 25 | size_t discsz; 26 | char **cmds; /* commands (e.g., httpd) */ 27 | size_t cmdsz; 28 | }; 29 | 30 | __BEGIN_DECLS 31 | 32 | struct sysinfo *sysinfo_alloc(void); 33 | int sysinfo_update(const struct syscfg *, struct sysinfo *); 34 | void sysinfo_free(struct sysinfo *); 35 | 36 | double sysinfo_get_cpu_avg(const struct sysinfo *); 37 | double sysinfo_get_mem_avg(const struct sysinfo *); 38 | int64_t sysinfo_get_nettx_avg(const struct sysinfo *); 39 | int64_t sysinfo_get_netrx_avg(const struct sysinfo *); 40 | int64_t sysinfo_get_discread_avg(const struct sysinfo *); 41 | int64_t sysinfo_get_discwrite_avg(const struct sysinfo *); 42 | double sysinfo_get_nfiles(const struct sysinfo *); 43 | double sysinfo_get_nprocs(const struct sysinfo *); 44 | double sysinfo_get_rprocs(const struct sysinfo *); 45 | time_t sysinfo_get_boottime(const struct sysinfo *); 46 | 47 | __END_DECLS 48 | 49 | #endif /* ! SLANT_COLLECT_H */ 50 | -------------------------------------------------------------------------------- /slant-upgrade.in.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # $Id$ 4 | # 5 | # Copyright (c) 2018 Kristaps Dzonsons 6 | # 7 | # Permission to use, copy, modify, and distribute this software for any 8 | # purpose with or without fee is hereby granted, provided that the above 9 | # copyright notice and this permission notice appear in all copies. 10 | # 11 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | # 19 | 20 | KWBP="@SHAREDIR@/slant/slant.kwbp" 21 | 22 | args=`getopt f: $*` 23 | if [ $? -ne 0 ] 24 | then 25 | echo 'Usage: ...' 26 | exit 2 27 | fi 28 | set -- $args 29 | while [ $# -ne 0 ] 30 | do 31 | case "$1" in 32 | -f) 33 | KWBP="$2"; shift; shift;; 34 | --) 35 | shift; break;; 36 | esac 37 | done 38 | 39 | if [ ! -f "@DATADIR@/slant.db" ] 40 | then 41 | # If the database doesn't exist, obviously nothing's running. 42 | # Simply install it and exit. 43 | set -e 44 | mkdir -p "@DATADIR@" 45 | echo "@DATADIR@/slant.db: installing new" 46 | ort-sql "$KWBP" | sqlite3 "@DATADIR@/slant.db" 47 | chown www "@DATADIR@/slant.db" 48 | chmod 600 "@DATADIR@/slant.db" 49 | install -m 0444 "$KWBP" "@DATADIR@/slant.kwbp" 50 | exit 0 51 | fi 52 | 53 | cmp -s "$KWBP" "@DATADIR@/slant.kwbp" 54 | if [ $? -eq 0 ] 55 | then 56 | echo "@DATADIR@/slant.db: already up to date" 57 | exit 0 58 | fi 59 | 60 | set -e 61 | TMPFILE=`mktemp` || exit 1 62 | trap "rm -f $TMPFILE" ERR EXIT 63 | 64 | echo "@DATADIR@/slant.db: patching existing" 65 | 66 | ( echo "BEGIN EXCLUSIVE TRANSACTION;" ; \ 67 | ort-sqldiff "@DATADIR@/slant.kwbp" "$KWBP" ; \ 68 | echo "COMMIT TRANSACTION;" ; ) > $TMPFILE 69 | 70 | if [ $? -ne 0 ] 71 | then 72 | echo "@DATADIR@/slant.db: patch aborted" 1>&2 73 | exit 1 74 | fi 75 | 76 | sqlite3 "@DATADIR@/slant.db" < $TMPFILE 77 | install -m 0444 "$KWBP" "@DATADIR@/slant.kwbp" 78 | rm -f "@DATADIR@/slant-upgrade.sql" 79 | echo "@DATADIR@/slant.db: patch success" 80 | -------------------------------------------------------------------------------- /slant-upgrade.8: -------------------------------------------------------------------------------- 1 | .\" $Id$ 2 | .\" 3 | .\" Copyright (c) 2018 Kristaps Dzonsons 4 | .\" 5 | .\" Permission to use, copy, modify, and distribute this software for any 6 | .\" purpose with or without fee is hereby granted, provided that the above 7 | .\" copyright notice and this permission notice appear in all copies. 8 | .\" 9 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | .\" 17 | .Dd $Mdocdate$ 18 | .Dt SLANT-UPGRADE 8 19 | .Os 20 | .Sh NAME 21 | .Nm slant-upgrade 22 | .Nd upgrade or install slant database 23 | .Sh SYNOPSIS 24 | .Nm slant-update 25 | .Op Fl f Ar config 26 | .Sh DESCRIPTION 27 | The 28 | .Nm 29 | utility upgrades or installs the database used by 30 | .Xr slant-collectd 8 31 | on 32 | .Ox . 33 | It accepts the 34 | .Fl f 35 | argument, which may be used to override the installed configuration. 36 | .Pp 37 | .Nm 38 | is usually run automatically by 39 | .Xr pkg_add 1 . 40 | .Pp 41 | For new installations (i.e., without an existing database file), this 42 | utility creates a database from the 43 | .Xr ort 5 44 | configuration. 45 | .Pp 46 | For upgrades, it patches the existing database file from both the 47 | installed and new 48 | .Xr ort 5 49 | configuration. 50 | .\" The following requests should be uncommented and used where appropriate. 51 | .\" .Sh CONTEXT 52 | .\" For section 9 functions only. 53 | .\" .Sh RETURN VALUES 54 | .\" For sections 2, 3, and 9 function return values only. 55 | .\" .Sh ENVIRONMENT 56 | .\" For sections 1, 6, 7, and 8 only. 57 | .\" .Sh FILES 58 | .\" .Sh EXIT STATUS 59 | .\" For sections 1, 6, and 8 only. 60 | .\" .Sh EXAMPLES 61 | .\" .Sh DIAGNOSTICS 62 | .\" For sections 1, 4, 6, 7, 8, and 9 printf/stderr messages only. 63 | .\" .Sh ERRORS 64 | .\" For sections 2, 3, 4, and 9 errno settings only. 65 | .Sh SEE ALSO 66 | .Xr slant 1 , 67 | .Xr slant-collectd 8 68 | .\" .Sh STANDARDS 69 | .\" .Sh HISTORY 70 | .\" .Sh AUTHORS 71 | .\" .Sh CAVEATS 72 | .\" .Sh BUGS 73 | -------------------------------------------------------------------------------- /slant-collectd.8: -------------------------------------------------------------------------------- 1 | .\" $Id$ 2 | .\" 3 | .\" Copyright (c) 2018 Kristaps Dzonsons 4 | .\" 5 | .\" Permission to use, copy, modify, and distribute this software for any 6 | .\" purpose with or without fee is hereby granted, provided that the above 7 | .\" copyright notice and this permission notice appear in all copies. 8 | .\" 9 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | .\" 17 | .Dd $Mdocdate$ 18 | .Dt SLANT-COLLECTD 8 19 | .Os 20 | .Sh NAME 21 | .Nm slant-collectd 22 | .Nd daemon to collect system statistics 23 | .Sh SYNOPSIS 24 | .Nm slant-collectd 25 | .Op Fl nv 26 | .Op Fl d Ar discs 27 | .Op Fl f Ar dbfile 28 | .Op Fl p Ar procs 29 | .Sh DESCRIPTION 30 | The 31 | .Nm 32 | utility collects system statistics and records them in 33 | .Ar dbfile , 34 | which defaults to 35 | .Pa /var/www/data/slant.db . 36 | Its arguments are as follows: 37 | .Bl -tag -width Ds 38 | .It Fl n 39 | Do not open the database: collect data only. 40 | .It Fl v 41 | Print collected data as a table to standard output. 42 | .It Fl d Ar discs 43 | Discs to monitor. 44 | Multiple discs may be separated by a comma. 45 | By default, all discs are monitored. 46 | .It Fl p Ar procs 47 | Processes to monitor. 48 | Multiple processes may be separated by a comma. 49 | Processes should be listed by their executable name, e.g., 50 | .Ar httpd 51 | instead of 52 | .Ar /usr/sbin/httpd . 53 | .It Fl f Ar dbfile 54 | The SQLite database file. 55 | .El 56 | .Pp 57 | To end collection, kill the process with 58 | .Dv SIGINT 59 | or 60 | .Dv SIGTERM , 61 | which will gracefully end the collection sequence. 62 | .\" The following requests should be uncommented and used where appropriate. 63 | .\" .Sh CONTEXT 64 | .\" For section 9 functions only. 65 | .\" .Sh RETURN VALUES 66 | .\" For sections 2, 3, and 9 function return values only. 67 | .\" .Sh ENVIRONMENT 68 | .\" For sections 1, 6, 7, and 8 only. 69 | .\" .Sh FILES 70 | .\" .Sh EXIT STATUS 71 | .\" For sections 1, 6, and 8 only. 72 | .Sh EXAMPLES 73 | On a system with software RAID (or software FDE), monitor only the root 74 | disc to prevent bandwidth duplication. 75 | .Bd -literal 76 | # slant-collectd -d sd0 77 | .Ed 78 | .Pp 79 | To additionally monitor CGI and login facilities: 80 | .Bd -literal 81 | # slant-collectd -d sd0 -p slowcgi,httpd,sshd 82 | .Ed 83 | .\" .Sh DIAGNOSTICS 84 | .\" For sections 1, 4, 6, 7, 8, and 9 printf/stderr messages only. 85 | .\" .Sh ERRORS 86 | .\" For sections 2, 3, 4, and 9 errno settings only. 87 | .\" .Sh SEE ALSO 88 | .\" .Xr foobar 1 89 | .\" .Sh STANDARDS 90 | .\" .Sh HISTORY 91 | .\" .Sh AUTHORS 92 | .\" .Sh CAVEATS 93 | .\" .Sh BUGS 94 | -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | html, body { margin: 0; 2 | height: 100%; } 3 | body { font-family: "Alegreya sans", sans-serif; 4 | font-weight: 400; 5 | color: #444; 6 | position: relative; } 7 | code { font-size: smaller; } 8 | footer { text-align: center; 9 | font-size: smaller; 10 | opacity: 0.75; 11 | padding: 3rem; } 12 | a { text-decoration: none; } 13 | body > header { background-color: #2c363f; 14 | padding-top: 6rem; 15 | padding-bottom: 1rem; 16 | box-shadow: 0px 1px 3px #8f6500; 17 | color: #fff; } 18 | body > header > div { display: flex; 19 | align-items: flex-end; } 20 | body > header > div > :first-child 21 | { flex: 1; } 22 | h1 { font-weight: 100; 23 | line-height: 50pt; 24 | font-size: 375%; 25 | margin: 0; } 26 | h2 { font-weight: 500; 27 | text-align: center; 28 | margin-top: 4rem; 29 | color: #000; 30 | font-variant: small-caps; } 31 | h3 { font-weight: inherit; 32 | text-align: center; 33 | color: #000; 34 | margin: 1.7em 0; 35 | font-size: 125%; 36 | font-variant: small-caps; } 37 | body > header .title { color: #aaa; } 38 | body > header .title a { color: #fff; 39 | font-weight: 500; } 40 | body > header nav a { color: #ffc530; 41 | display: inline-block; 42 | font-weight: 500; } 43 | body > header nav a + a { margin-left: 0.5rem; } 44 | body > header nav .hilite { border: thin solid rgba(255, 165, 0, 0.5); 45 | transition: border 0.2s linear; 46 | border-radius: 24pt; 47 | padding: 0.1em 0.4em; } 48 | body > header a:hover { text-decoration: underline; } 49 | body > header a.hilite:hover { text-decoration: none; } 50 | body > header .hilite:hover { border: thin solid rgba(255, 165, 0, 1.0); } 51 | 52 | .screens div { flex: 1.01; } 53 | .screens div:nth-child(2) { flex: 2; 54 | margin-left: 1rem; } 55 | .screens div:nth-child(3) { flex: 1.42; 56 | margin-left: 1rem; } 57 | .screens img { box-shadow: 1px 1px 4px orange; 58 | cursor: pointer; 59 | width: 100%; } 60 | .screens { display: flex; } 61 | .graphs { text-align: center; } 62 | .graphs img { width: 100%; } 63 | #index1 img { max-width: 450px; } 64 | .tagline { margin-top: 4rem; 65 | text-align: center; 66 | font-size: 150%; 67 | color: #2c363f; } 68 | .version { margin-bottom: 4rem; 69 | text-align: center; 70 | color: #aaa; } 71 | .version span { color: #666; } 72 | strong { font-weight: 500; 73 | color: #000; } 74 | .nm { font-style: italic; } 75 | 76 | .popup .pop { display: none; } 77 | .popup .pop img { border: none; 78 | width: 90%; } 79 | #screen2 img { max-width: 1024px; } 80 | #screen3 img, 81 | #screen1 img { max-width: 1280px; } 82 | .popup.shown .pop { position: fixed; 83 | width: 100%; 84 | cursor: pointer; 85 | z-index: 5; 86 | display: flex; 87 | align-items: center; 88 | justify-content: center; 89 | background-color: rgba(255, 255, 255, 0.9); 90 | text-align: center; 91 | height: 100%; 92 | left: 0; 93 | top: 0; } 94 | nav[data-sblg-nav] ul { list-style-type: none; 95 | padding: 0; } 96 | nav[data-sblg-nav] header { opacity: 0.7; 97 | border-bottom: thin solid #aaa; } 98 | nav[data-sblg-nav] p { margin: 0.5em 0; } 99 | nav[data-sblg-nav] li { opacity: 0.75; } 100 | nav[data-sblg-nav] li:first-child 101 | { opacity: 1; 102 | margin-bottom: 1em; } 103 | 104 | @media only screen and (max-width: 768px), only screen and (max-device-width: 768px) { 105 | .tagline { font-size: inherit; } 106 | body > header > div { display: block; } 107 | article > ul { padding: 0; } 108 | body > header { padding-top: 2rem; 109 | padding-bottom: 0.5rem; } 110 | body > header nav { padding-top: 1rem; } 111 | -------------------------------------------------------------------------------- /slant.kwbp: -------------------------------------------------------------------------------- 1 | # vim: set smartindent: 2 | # vim: set filetype=kwbp: 3 | # 4 | # $Id$ 5 | # 6 | # Copyright (c) 2018 Kristaps Dzonsons 7 | # 8 | # Permission to use, copy, modify, and distribute this software for any 9 | # purpose with or without fee is hereby granted, provided that the above 10 | # copyright notice and this permission notice appear in all copies. 11 | # 12 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | 20 | roles { 21 | role produce; 22 | role consume; 23 | }; 24 | 25 | enum interval { 26 | item byqmin comment "Per-quarter-minute record type."; 27 | item bymin comment "Per-minute record type."; 28 | item byhour comment "Per-hour record type."; 29 | item byday comment "Per-day record type."; 30 | item byweek comment "Per-week record type."; 31 | item byyear comment "Per-year record type."; 32 | }; 33 | 34 | struct system { 35 | field boot epoch comment 36 | "When the system was last booted."; 37 | field machine text null comment 38 | "Machine hardware platform."; 39 | field osversion text null comment 40 | "Version level of the operating system."; 41 | field osrelease text null comment 42 | "Release level of the operating system."; 43 | field sysname text null comment 44 | "Operating system name."; 45 | field id int unique default 1; 46 | 47 | insert; 48 | 49 | update boot, machine, osversion, osrelease, sysname: id: name all; 50 | 51 | search id: name id; 52 | 53 | roles produce { 54 | insert; 55 | update all; 56 | search id; 57 | }; 58 | 59 | roles consume { 60 | search id; 61 | }; 62 | }; 63 | 64 | struct record { 65 | field ctime epoch comment 66 | "Time the record started. 67 | So for an hour record, this is the time the hourly 68 | record keeping began."; 69 | field entries int comment 70 | "The number of samples represented by the record, when 71 | the record is an accumulation (minute, hour, etc.). 72 | This is always non-zero."; 73 | field cpu double comment 74 | "The percentage aggregate average CPU time within the 75 | current record. 76 | CPU time is any non-idle category. 77 | This is recorded across all cores/CPUs on the system."; 78 | field mem double comment 79 | "Percentage of active memory over available memory 80 | (active over npages, in uvmexp.h terms)."; 81 | field nettx int comment 82 | "Transmitted bytes per second over all interfaces. 83 | Only non-loopback devices in up mode are counted."; 84 | field netrx int comment 85 | "Received bytes per second over all interfaces. 86 | Only non-loopback devices in up mode are counted."; 87 | field discread int comment 88 | "Read bytes per second over all configured discs."; 89 | field discwrite int comment 90 | "Written bytes per second over all configured discs."; 91 | field nprocs double default 0 comment 92 | "The percentage of processes running over the maximum 93 | number of possible processes."; 94 | field rprocs double default 0 comment 95 | "The percentage of matches over all configured 96 | commands to search for."; 97 | field nfiles double default 0 comment 98 | "The percentage of file descriptors over the maximum 99 | number of possible descriptors."; 100 | 101 | field interval enum interval comment 102 | "The type of record."; 103 | field id int rowid comment 104 | "The unique identifier (not used except when updating 105 | the tail entry)."; 106 | 107 | insert; 108 | 109 | list: name lister order ctime desc comment 110 | "List all entries, ordered by record time."; 111 | 112 | update ctime, entries, cpu, mem, nettx, netrx, discread, 113 | discwrite, nprocs, rprocs, nfiles: id: 114 | name tail comment 115 | "Take the tail of the circular queue and refresh its 116 | contents, making it the new head."; 117 | update entries, cpu, mem, nettx, netrx, discread, discwrite, 118 | nprocs, rprocs, nfiles: id: name current comment 119 | "Update the current record. 120 | This is the record within the current quarter-minute 121 | (if qmin), minute (if min), or hour (if hour)."; 122 | 123 | roles produce { 124 | insert; 125 | list lister; 126 | update tail; 127 | update current; 128 | }; 129 | 130 | roles consume { 131 | list lister; 132 | }; 133 | }; 134 | -------------------------------------------------------------------------------- /slant-cgi.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2018 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #if HAVE_SYS_QUEUE 20 | # include 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | 35 | #include "params.h" 36 | #include "extern.h" 37 | #include "db.h" 38 | #include "json.h" 39 | 40 | enum page { 41 | PAGE_INDEX, 42 | PAGE__MAX 43 | }; 44 | 45 | static const char *const pages[PAGE__MAX] = { 46 | "index", /* PAGE_INDEX */ 47 | }; 48 | 49 | /* 50 | * Fill out generic headers then start the HTTP document body (no more 51 | * headers after this point!) 52 | */ 53 | static void 54 | http_open(struct kreq *r, enum khttp code) 55 | { 56 | 57 | khttp_head(r, kresps[KRESP_STATUS], 58 | "%s", khttps[code]); 59 | khttp_head(r, kresps[KRESP_CONTENT_TYPE], 60 | "%s", kmimetypes[r->mime]); 61 | khttp_body(r); 62 | } 63 | 64 | static void 65 | sendindex(struct kreq *r, 66 | const struct system *sys, const struct record_q *q) 67 | { 68 | struct kjsonreq req; 69 | const struct record *rr; 70 | 71 | http_open(r, KHTTP_200); 72 | kjson_open(&req, r); 73 | kjson_obj_open(&req); 74 | 75 | kjson_putstringp(&req, "version", VERSION); 76 | kjson_putintp(&req, "timestamp", time(NULL)); 77 | 78 | json_system_obj(&req, sys); 79 | 80 | kjson_arrayp_open(&req, "qmin"); 81 | TAILQ_FOREACH(rr, q, _entries) 82 | if (INTERVAL_byqmin == rr->interval) { 83 | kjson_obj_open(&req); 84 | json_record_data(&req, rr); 85 | kjson_obj_close(&req); 86 | } 87 | kjson_array_close(&req); 88 | kjson_arrayp_open(&req, "min"); 89 | TAILQ_FOREACH(rr, q, _entries) 90 | if (INTERVAL_bymin == rr->interval) { 91 | kjson_obj_open(&req); 92 | json_record_data(&req, rr); 93 | kjson_obj_close(&req); 94 | } 95 | kjson_array_close(&req); 96 | kjson_arrayp_open(&req, "hour"); 97 | TAILQ_FOREACH(rr, q, _entries) 98 | if (INTERVAL_byhour == rr->interval) { 99 | kjson_obj_open(&req); 100 | json_record_data(&req, rr); 101 | kjson_obj_close(&req); 102 | } 103 | kjson_array_close(&req); 104 | kjson_arrayp_open(&req, "day"); 105 | TAILQ_FOREACH(rr, q, _entries) 106 | if (INTERVAL_byday == rr->interval) { 107 | kjson_obj_open(&req); 108 | json_record_data(&req, rr); 109 | kjson_obj_close(&req); 110 | } 111 | kjson_array_close(&req); 112 | kjson_arrayp_open(&req, "week"); 113 | TAILQ_FOREACH(rr, q, _entries) 114 | if (INTERVAL_byweek == rr->interval) { 115 | kjson_obj_open(&req); 116 | json_record_data(&req, rr); 117 | kjson_obj_close(&req); 118 | } 119 | kjson_array_close(&req); 120 | kjson_arrayp_open(&req, "year"); 121 | TAILQ_FOREACH(rr, q, _entries) 122 | if (INTERVAL_byyear == rr->interval) { 123 | kjson_obj_open(&req); 124 | json_record_data(&req, rr); 125 | kjson_obj_close(&req); 126 | } 127 | kjson_array_close(&req); 128 | 129 | kjson_obj_close(&req); 130 | kjson_close(&req); 131 | } 132 | 133 | int 134 | main(void) 135 | { 136 | struct kreq r; 137 | enum kcgi_err er; 138 | struct record_q *rq; 139 | struct system *sys; 140 | 141 | #if HAVE_PLEDGE 142 | if (-1 == pledge("stdio rpath " 143 | "cpath wpath flock fattr proc", NULL)) { 144 | kutil_warn(NULL, NULL, "pledge"); 145 | return EXIT_FAILURE; 146 | } 147 | #endif 148 | 149 | er = khttp_parsex(&r, ksuffixmap, 150 | kmimetypes, KMIME__MAX, NULL, 0, 151 | pages, PAGE__MAX, KMIME_APP_JSON, 152 | PAGE_INDEX, NULL, NULL, 0, NULL); 153 | 154 | if (KCGI_OK != er) { 155 | kutil_warnx(NULL, NULL, "%s", kcgi_strerror(er)); 156 | return EXIT_FAILURE; 157 | } 158 | 159 | /* 160 | * Front line of defence: make sure we're a proper method, make 161 | * sure we're a page, make sure we're a JSON file. 162 | */ 163 | 164 | if (KMETHOD_GET != r.method) { 165 | http_open(&r, KHTTP_405); 166 | khttp_free(&r); 167 | return EXIT_SUCCESS; 168 | } else if (PAGE__MAX == r.page || 169 | KMIME_APP_JSON != r.mime) { 170 | http_open(&r, KHTTP_404); 171 | khttp_free(&r); 172 | return EXIT_SUCCESS; 173 | } 174 | 175 | if (NULL == (r.arg = db_open(DBFILE))) { 176 | khttp_free(&r); 177 | return EXIT_SUCCESS; 178 | } 179 | 180 | #if HAVE_PLEDGE 181 | if (-1 == pledge("stdio", NULL)) { 182 | kutil_warn(NULL, NULL, "pledge"); 183 | db_close(r.arg); 184 | khttp_free(&r); 185 | return EXIT_FAILURE; 186 | } 187 | #endif 188 | 189 | db_role(r.arg, ROLE_consume); 190 | 191 | rq = db_record_list_lister(r.arg); 192 | sys = db_system_get_id(r.arg, 1); 193 | 194 | sendindex(&r, sys, rq); 195 | 196 | db_system_free(sys); 197 | db_record_freeq(rq); 198 | 199 | db_close(r.arg); 200 | khttp_free(&r); 201 | 202 | return EXIT_SUCCESS; 203 | } 204 | -------------------------------------------------------------------------------- /slant-collectd-freebsd.c: -------------------------------------------------------------------------------- 1 | #ifdef __FreeBSD__ 2 | /* $Id$ */ 3 | #include "config.h" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "slant-collectd.h" 19 | 20 | #define GETSYSCTL(name, var) \ 21 | getsysctl(name, &(var), sizeof(var)) 22 | 23 | struct sysinfo { 24 | size_t ncpu; /* total number of cpus */ 25 | long *pcpu_cp_time; /* used for cpu compute */ 26 | long *pcpu_cp_old; /* used for cpu compute */ 27 | long *pcpu_cp_diff; /* used for cpu compute */ 28 | int *pcpu_cpu_states; /* used for cpu compute */ 29 | time_t boottime; /* time booted */ 30 | 31 | double mem_avg; /* average memory */ 32 | double cpu_avg; /* average cpu */ 33 | double nproc_pct; /* nprocs percent */ 34 | double nfile_pct; /* nfiles percent */ 35 | double rproc_pct; /* pct command (by name) found */ 36 | u_int64_t disc_rbytes; /* last disc total read */ 37 | u_int64_t disc_wbytes; /* last disc total write */ 38 | int64_t disc_ravg; /* average reads/sec */ 39 | int64_t disc_wavg; /* average reads/sec */ 40 | }; 41 | 42 | /* 43 | * Get the number of configured CPUs. 44 | * This might be greater than the number of working CPUs. 45 | * Return zero on failure, non-zero on success. 46 | * Fills in "p" on success. 47 | */ 48 | static int 49 | sysinfo_getncpu(size_t *p) 50 | { 51 | int numcpu; 52 | 53 | if (-1 == GETSYSCTL("kern.smp.maxcpus", maxcpu)) { 54 | warn("sysctl: kern.smp.maxcpus"); 55 | return 0; 56 | } 57 | 58 | assert(numcpu > 0); 59 | *p = numcpu; 60 | return 1; 61 | } 62 | 63 | static int 64 | sysinfo_init_boottime(struct sysinfo *p) 65 | { 66 | struct timeval tv; 67 | 68 | if (-1 == GETSYSCTL("kern.boottime", tv)) { 69 | warn("sysctl: kern.boottime"); 70 | return 0; 71 | } 72 | 73 | p->boottime = tv.tv_sec; 74 | return 1; 75 | } 76 | 77 | struct sysinfo * 78 | sysinfo_alloc(void) 79 | { 80 | struct sysinfo *p; 81 | size_t size; 82 | 83 | p = calloc(1, sizeof(struct sysinfo)); 84 | if (NULL == p) { 85 | warn(NULL); 86 | return NULL; 87 | } else if ( ! sysinfo_getncpu(&p->ncpu)) { 88 | warn(NULL); 89 | sysinfo_free(p); 90 | return NULL; 91 | } 92 | 93 | size = sizeof(long) * p->ncpu * CPUSTATES; 94 | 95 | p->pcpu_cp_time = calloc(1, size); 96 | p->pcpu_cp_old = calloc(p->ncpu * CPUSTATES, sizeof(long)); 97 | p->pcpu_cp_diff = calloc(p->ncpu * CPUSTATES, sizeof(long)); 98 | p->pcpu_cpu_states = calloc(p->ncpu * CPUSTATES, sizeof(int)); 99 | 100 | if (NULL == p->pcpu_cp_time || 101 | NULL == p->pcpu_cp_old || 102 | NULL == p->pcpu_cp_diff || 103 | NULL == p->pcpu_cpu_states) { 104 | warn(NULL); 105 | sysinfo_free(p); 106 | return NULL; 107 | } 108 | 109 | if ( ! sysinfo_init_boottime(p)) { 110 | sysinfo_free(p); 111 | return NULL; 112 | } 113 | 114 | return p; 115 | } 116 | 117 | static int 118 | sysinfo_update_mem(struct sysinfo *p) 119 | { 120 | } 121 | 122 | static int 123 | sysinfo_update_nfiles(const struct syscfg *cfg, struct sysinfo *p) 124 | { 125 | } 126 | 127 | static int 128 | sysinfo_update_nprocs(const struct syscfg *cfg, struct sysinfo *p) 129 | { 130 | } 131 | 132 | static int 133 | sysinfo_update_cpu(struct sysinfo *p) 134 | { 135 | } 136 | 137 | static int 138 | sysinfo_update_if(struct sysinfo *p) 139 | { 140 | } 141 | 142 | static int 143 | sysinfo_update_disc(const struct syscfg *cfg, struct sysinfo *p) 144 | { 145 | } 146 | 147 | int 148 | sysinfo_update(const struct syscfg *cfg, struct sysinfo *p) 149 | { 150 | 151 | if ( ! sysinfo_update_nprocs(cfg, p)) 152 | return 0; 153 | if ( ! sysinfo_update_nfiles(cfg, p)) 154 | return 0; 155 | if ( ! sysinfo_update_cpu(p)) 156 | return 0; 157 | if ( ! sysinfo_update_mem(p)) 158 | return 0; 159 | if ( ! sysinfo_update_if(p)) 160 | return 0; 161 | if ( ! sysinfo_update_disc(cfg, p)) 162 | return 0; 163 | 164 | p->sample++; 165 | return 1; 166 | } 167 | 168 | double 169 | sysinfo_get_cpu_avg(const struct sysinfo *p) 170 | { 171 | 172 | return p->cpu_avg; 173 | } 174 | 175 | double 176 | sysinfo_get_mem_avg(const struct sysinfo *p) 177 | { 178 | 179 | return p->mem_avg; 180 | } 181 | 182 | int64_t 183 | sysinfo_get_nettx_avg(const struct sysinfo *p) 184 | { 185 | 186 | if (1 == p->sample) 187 | return 0; 188 | return p->ifsum.ifc_ob; 189 | } 190 | 191 | int64_t 192 | sysinfo_get_netrx_avg(const struct sysinfo *p) 193 | { 194 | 195 | if (1 == p->sample) 196 | return 0; 197 | return p->ifsum.ifc_ib; 198 | } 199 | 200 | int64_t 201 | sysinfo_get_discread_avg(const struct sysinfo *p) 202 | { 203 | 204 | if (1 == p->sample) 205 | return 0; 206 | return p->disc_ravg; 207 | } 208 | 209 | int64_t 210 | sysinfo_get_discwrite_avg(const struct sysinfo *p) 211 | { 212 | 213 | if (1 == p->sample) 214 | return 0; 215 | return p->disc_wavg; 216 | } 217 | 218 | double 219 | sysinfo_get_rprocs(const struct sysinfo *p) 220 | { 221 | 222 | return p->rproc_pct; 223 | } 224 | 225 | double 226 | sysinfo_get_nfiles(const struct sysinfo *p) 227 | { 228 | 229 | return p->nfile_pct; 230 | } 231 | 232 | double 233 | sysinfo_get_nprocs(const struct sysinfo *p) 234 | { 235 | 236 | return p->nproc_pct; 237 | } 238 | 239 | time_t 240 | sysinfo_get_boottime(const struct sysinfo *p) 241 | { 242 | 243 | return p->boottime; 244 | } 245 | 246 | void 247 | sysinfo_free(struct sysinfo *p) 248 | { 249 | size_t i; 250 | 251 | if (NULL == p) 252 | return; 253 | 254 | free(p->pcpu_cp_time); 255 | free(p->pcpu_cp_old); 256 | free(p->pcpu_cp_diff); 257 | free(p->pcpu_cpu_states); 258 | free(p); 259 | } 260 | 261 | #endif 262 | 263 | -------------------------------------------------------------------------------- /slant-cgi.8: -------------------------------------------------------------------------------- 1 | .\" $Id$ 2 | .\" 3 | .\" Copyright (c) 2018 Kristaps Dzonsons 4 | .\" 5 | .\" Permission to use, copy, modify, and distribute this software for any 6 | .\" purpose with or without fee is hereby granted, provided that the above 7 | .\" copyright notice and this permission notice appear in all copies. 8 | .\" 9 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | .\" 17 | .Dd $Mdocdate$ 18 | .Dt SLANT-CGI 8 19 | .Os 20 | .Sh NAME 21 | .Nm slant-cgi 22 | .Nd CGI program returning slant data 23 | .Sh SYNOPSIS 24 | .Nm slant-cgi 25 | .Sh DESCRIPTION 26 | The 27 | .Nm 28 | utility produces system statistics as recorded by 29 | .Xr slant-collectd 8 . 30 | It interfaces with the database by default in 31 | .Pa /var/www/data/slant.db . 32 | .Pp 33 | The CGI program accepts HTTP GET requests for the 34 | .Pa /index.json 35 | resource, which has the aliases 36 | .Pa /index , 37 | .Pa / , 38 | or the empty request. 39 | Other resources return an HTTP code 404. 40 | Non-GET request return an HTTP code 405. 41 | Other (non-200) codes are possible and follow standard definitions. 42 | .Pp 43 | On success, 44 | .Nm 45 | returns an HTTP code 200 and a valid JSON document. 46 | .Pp 47 | The document consists of the following. 48 | In this description, integers 49 | .Pq Li int 50 | are serialised from 64-bit signed numbers. 51 | Real-valued numbers 52 | .Pq Li real 53 | are serialised from double-precision numbers. 54 | .Bd -literal 55 | { version: "x.y.z", 56 | timestamp: int, 57 | system: { system }, 58 | qmin: [ records... ], 59 | min: [ records... ], 60 | hour: [ records... ], 61 | day: [ records... ], 62 | week: [ records... ], 63 | year: [ records... ] 64 | } 65 | .Ed 66 | .Pp 67 | The 68 | .Li version 69 | is the software suite version. 70 | This is informational. 71 | .Pp 72 | The 73 | .Li system 74 | object consists of system information: 75 | .Bd -literal 76 | { boot: int, 77 | machine: string, 78 | osversion: string, 79 | osrelease: string, 80 | sysname: string 81 | } 82 | .Ed 83 | .Pp 84 | It consists only of 85 | .Li boot , 86 | the UNIX epoch value for when the system was last booted. 87 | The 88 | .Li machine , 89 | .Li osversion , 90 | .Li osrelease , 91 | and 92 | .Li sysname 93 | mirror the return values of 94 | .Xr uname 1 . 95 | .Pp 96 | The remaining values are the possibly-empty sets of records accumulated 97 | over a given interval of time in quarter-minute quanta. 98 | So each 99 | .Li qmin 100 | record consists of exactly one entry; each 101 | .Li min 102 | record consists of at least one and at most four entries, one for each 103 | quarter-minute; each 104 | .Li hour 105 | has at most 240, etc. 106 | .Pp 107 | There are finite records (except for yearly ones) bound to a circular 108 | buffer per interval. 109 | When a record is filled, such as with four quarter-minute entries into a 110 | minute record, the next entry creates a new record with one entry while 111 | the oldest is discarded. 112 | .Pp 113 | The circular buffer is large enough for a reasonable glimpse into the 114 | past, with emphasis placed on recent data: 40 quarter-minute records (10 115 | minutes), 300 minute records (5 hours), 96 hourly (5 days), 28 daily (4 116 | weeks), 104 weekly (two years), endless yearly entries. 117 | .Pp 118 | Each record consists of the following: 119 | .Bd -literal 120 | { ctime: int, 121 | entries: int, 122 | cpu: real, 123 | mem: real, 124 | netrx: int, 125 | nettx: int, 126 | discread: int, 127 | discwrite: int, 128 | nprocs: real, 129 | rprocs: real, 130 | nfiles: real, 131 | interval: int, 132 | id: int 133 | } 134 | .Ed 135 | .Pp 136 | The fields are defined as follows: 137 | .Bl -tag -width Ds 138 | .It Li ctime 139 | a UNIX epoch of when the record was started (first entry) 140 | .It Li entries 141 | the number of entries, so front-end displays will need to average all 142 | data by this number 143 | .It Li cpu 144 | average processor utilisation over all processing unit 145 | .It Li mem 146 | active memory over all available memory 147 | .It Li netrx 148 | bytes received per second over all interfaces 149 | .It Li nettx 150 | bytes transmitted per second over all interfaces 151 | .It Li discread 152 | bytes read per second over all configured devices 153 | .It Li discwrite 154 | bytes written per second over all configured devices 155 | .It Li nprocs 156 | number of running processes over all possible processes 157 | .It Li rprocs 158 | number of configured processes running over total configured 159 | .It Li nfiles 160 | number of open files over all possible open files 161 | .It Li interval 162 | the type of interval starting with zero for quarter-minute, one fo 163 | minute, etc. 164 | .It Li id 165 | a unique record identifier 166 | .El 167 | .\" The following requests should be uncommented and used where appropriate. 168 | .\" .Sh CONTEXT 169 | .\" For section 9 functions only. 170 | .\" .Sh RETURN VALUES 171 | .\" For sections 2, 3, and 9 function return values only. 172 | .\" .Sh ENVIRONMENT 173 | .\" For sections 1, 6, 7, and 8 only. 174 | .\" .Sh FILES 175 | .\" .Sh EXIT STATUS 176 | .\" For sections 1, 6, and 8 only. 177 | .\" .Sh EXAMPLES 178 | .\" .Sh DIAGNOSTICS 179 | .\" For sections 1, 4, 6, 7, 8, and 9 printf/stderr messages only. 180 | .\" .Sh ERRORS 181 | .\" For sections 2, 3, 4, and 9 errno settings only. 182 | .Sh SEE ALSO 183 | .Xr slant 1 , 184 | .Xr slant-collectd 8 185 | .\" .Sh STANDARDS 186 | .\" .Sh HISTORY 187 | .\" .Sh AUTHORS 188 | .\" .Sh CAVEATS 189 | .\" .Sh BUGS 190 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .SUFFIXES: .8 .8.html .1 .1.html .dot .svg .ts .js 2 | 3 | include Makefile.configure 4 | 5 | WPREFIX = /var/www 6 | CGIBIN = $(WPREFIX)/cgi-bin 7 | DATADIR = $(WPREFIX)/data 8 | 9 | DBFILE = /data/slant.db 10 | WWWDIR = /var/www/vhosts/kristaps.bsd.lv/htdocs/slant 11 | 12 | # Additional libraries required per component. 13 | # These should be set in Makefile.local. 14 | 15 | LDADD_SLANT_COLLECTD = 16 | LDADD_SLANT_CGI = 17 | LDADD_SLANT = 18 | 19 | sinclude Makefile.local 20 | 21 | VERSION = 0.0.24 22 | CPPFLAGS += -DVERSION=\"$(VERSION)\" 23 | 24 | WWW = index.html \ 25 | index.js \ 26 | index1.svg \ 27 | slant.1.html \ 28 | slant-cgi.8.html \ 29 | slant-collectd.8.html 30 | DOTAR = compats.c \ 31 | tests.c \ 32 | Makefile \ 33 | slant-cgi.c \ 34 | slant-cgi.8 \ 35 | slant-collectd-freebsd.c \ 36 | slant-collectd-linux.c \ 37 | slant-collectd-openbsd.c \ 38 | slant-collectd.8 \ 39 | slant-collectd.c \ 40 | slant-collectd.h \ 41 | slant-config.c \ 42 | slant-dns.c \ 43 | slant-draw.c \ 44 | slant-http.c \ 45 | slant-json.c \ 46 | slant-upgrade.in.sh \ 47 | slant-upgrade.8 \ 48 | slant.1 \ 49 | slant.c \ 50 | slant.h \ 51 | slant.kwbp 52 | SLANT_OBJS = slant.o \ 53 | slant-config.o \ 54 | slant-dns.o \ 55 | slant-draw.o \ 56 | slant-http.o \ 57 | slant-json.o \ 58 | json.o 59 | SLANT_COLLECTD_OBJS = \ 60 | compats.o \ 61 | db.o \ 62 | slant-collectd.o \ 63 | slant-collectd-freebsd.o \ 64 | slant-collectd-linux.o \ 65 | slant-collectd-openbsd.o 66 | OBJS = $(SLANT_OBJS) \ 67 | slant-cgi.o \ 68 | slant-collectd.o \ 69 | slant-collectd-freebsd.o \ 70 | slant-collectd-linux.o \ 71 | slant-collectd-openbsd.o 72 | 73 | # Needed on FreeBSD. 74 | CFLAGS += $(CPPFLAGS) 75 | 76 | all: slant.db slant-collectd slant-cgi slant slant-upgrade 77 | 78 | www: slant.tar.gz $(WWW) 79 | 80 | installwww: www 81 | mkdir -p $(WWWDIR) 82 | mkdir -p $(WWWDIR)/snapshots 83 | $(INSTALL_DATA) slant.tar.gz $(WWWDIR)/snapshots/slant-$(VERSION).tar.gz 84 | $(INSTALL_DATA) slant.tar.gz $(WWWDIR)/snapshots 85 | $(INSTALL_DATA) $(WWW) index.css screen1.jpg screen2.jpg screen3.jpg $(WWWDIR) 86 | 87 | slant.tar.gz: $(DOTAR) configure 88 | mkdir -p .dist/slant-$(VERSION)/ 89 | install -m 0755 configure .dist/slant-$(VERSION) 90 | install -m 0644 $(DOTAR) .dist/slant-$(VERSION) 91 | ( cd .dist/ && tar zcf ../$@ ./ ) 92 | rm -rf .dist/ 93 | 94 | install: slant-collectd slant-cgi slant slant-upgrade 95 | mkdir -p $(DESTDIR)$(SHAREDIR)/slant 96 | mkdir -p $(DESTDIR)$(BINDIR) 97 | mkdir -p $(DESTDIR)$(SBINDIR) 98 | mkdir -p $(DESTDIR)$(MANDIR)/man1 99 | mkdir -p $(DESTDIR)$(CGIBIN) 100 | $(INSTALL_DATA) slant.kwbp $(DESTDIR)$(SHAREDIR)/slant 101 | $(INSTALL_PROGRAM) slant-cgi $(DESTDIR)$(CGIBIN) 102 | $(INSTALL_PROGRAM) slant-collectd slant-upgrade $(DESTDIR)$(SBINDIR) 103 | $(INSTALL_PROGRAM) slant $(DESTDIR)$(BINDIR) 104 | $(INSTALL_MAN) slant.1 $(DESTDIR)$(MANDIR)/man1 105 | $(INSTALL_MAN) slant-cgi.8 slant-collectd.8 slant-upgrade.8 $(DESTDIR)$(MANDIR)/man8 106 | 107 | uninstall: 108 | rm -f $(DESTDIR)$(SHAREDIR)/slant/slant.kwbp 109 | rmdir $(DESTDIR)$(SHAREDIR)/slant 110 | rm -f $(DESTDIR)$(CGIBIN)/slant-cgi 111 | rm -f $(DESTDIR)$(SBINDIR)/slant-collectd 112 | rm -f $(DESTDIR)$(SBINDIR)/slant-upgrade 113 | rm -f $(DESTDIR)$(BINDIR)/slant 114 | rm -f $(DESTDIR)$(MANDIR)/man1/slant.1 115 | rm -f $(DESTDIR)$(MANDIR)/man8/slant-cgi.8 116 | rm -f $(DESTDIR)$(MANDIR)/man8/slant-collectd.8 117 | rm -f $(DESTDIR)$(MANDIR)/man8/slant-upgrade.8 118 | 119 | slant-upgrade: slant-upgrade.in.sh 120 | sed -e "s!@DATADIR@!$(DATADIR)!g" \ 121 | -e "s!@CGIBIN@!$(CGIBIN)!g" \ 122 | -e "s!@SHAREDIR@!$(SHAREDIR)!g" slant-upgrade.in.sh >$@ 123 | 124 | slant-collectd: $(SLANT_COLLECTD_OBJS) 125 | $(CC) -o $@ $(LDFLAGS) $(SLANT_COLLECTD_OBJS) -lsqlbox -lsqlite3 $(LDADD_SLANT_COLLECTD) 126 | 127 | params.h: 128 | echo "#define DBFILE \"$(DBFILE)\"" > params.h 129 | 130 | slant-cgi: slant-cgi.o db.o json.o compats.o 131 | $(CC) -static -o $@ $(LDFLAGS) slant-cgi.o db.o json.o compats.o -lkcgi -lkcgijson -lz -lsqlbox -lsqlite3 -lm -lpthread $(LDADD_SLANT_CGI) 132 | 133 | slant-cgi.o: params.h 134 | 135 | slant: $(SLANT_OBJS) 136 | $(CC) -o $@ $(LDFLAGS) $(SLANT_OBJS) -ltls -lncurses -lkcgijson -lkcgi -lz $(LDADD_SLANT) 137 | 138 | clean: 139 | rm -f slant.db slant.sql slant.tar.gz slant-upgrade 140 | rm -f db.c db.h json.c json.h extern.h params.h 141 | rm -f slant-collectd slant-cgi slant 142 | rm -f $(OBJS) compats.o db.o json.o 143 | rm -f $(WWW) 144 | 145 | distclean: clean 146 | rm -f Makefile.configure config.h config.log 147 | 148 | slant.db: slant.sql 149 | rm -f $@ 150 | sqlite3 $@ < slant.sql 151 | 152 | slant.sql: slant.kwbp 153 | ort-sql slant.kwbp > $@ 154 | 155 | slant-collectd-openbsd.o slant-collectd-linux.o slant-collectd.o: slant-collectd.h 156 | 157 | db.o slant-collectd.o slant-cgi.o: db.h 158 | 159 | json.o slant-cgi.o slant-json.o slant.o: json.h 160 | 161 | $(OBJS): config.h 162 | 163 | $(SLANT_OBJS): slant.h 164 | 165 | db.h: extern.h 166 | 167 | json.h: extern.h 168 | 169 | slant.h: extern.h 170 | 171 | extern.h: slant.kwbp 172 | ort-c-header -g EXTERN_H -Nd slant.kwbp > $@ 173 | 174 | db.h: slant.kwbp 175 | ort-c-header -Nb slant.kwbp > $@ 176 | 177 | json.h: slant.kwbp 178 | ort-c-header -g JSON_H -jJ -Nbd slant.kwbp > $@ 179 | 180 | db.c: slant.kwbp 181 | ort-c-source -h extern.h,db.h slant.kwbp > $@ 182 | 183 | json.c: slant.kwbp 184 | ort-c-source -Ij -jJ -Nd -h extern.h,json.h slant.kwbp > $@ 185 | 186 | index.html: index.xml versions.xml 187 | sblg -o $@ -t index.xml versions.xml 188 | 189 | .8.8.html .1.1.html: 190 | mandoc -Ostyle=https://bsd.lv/css/mandoc.css -Thtml $< >$@ 191 | 192 | .dot.svg: 193 | dot -Tsvg $< | xsltproc --novalid notugly.xsl - >$@ 194 | 195 | .ts.js: 196 | tsc --strict --alwaysStrict --removeComments --outFile $@ $< 197 | -------------------------------------------------------------------------------- /slant-dns.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2018 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #if HAVE_SYS_QUEUE 20 | # include 21 | #endif 22 | #include 23 | #include 24 | #include 25 | 26 | #if HAVE_ERR 27 | # include 28 | #endif 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include "extern.h" 39 | #include "slant.h" 40 | 41 | /* 42 | * Parse the url in n->url into its component parts. 43 | * This is a non-canonical parse that favours simplicity: we only want 44 | * to know about the scheme, domain, username/password, port, and path 45 | * (plus optional query string). 46 | */ 47 | void 48 | dns_parse_url(struct out *out, struct node *n) 49 | { 50 | char *cp, *httpauth = NULL; 51 | const char *s = n->url, *er; 52 | size_t pos, targsz, srcsz; 53 | int c; 54 | 55 | n->addrs.https = 0; 56 | n->addrs.port = 80; 57 | 58 | /* Start with our scheme. */ 59 | 60 | if (0 == strncasecmp(s, "https://", 8)) { 61 | n->addrs.https = 1; 62 | n->addrs.port = 443; 63 | s += 8; 64 | } else if (0 == strncasecmp(s, "http://", 7)) 65 | s += 7; 66 | 67 | if (NULL == (n->host = strdup(s))) 68 | err(EXIT_FAILURE, NULL); 69 | 70 | /* 71 | * The path part starts with either a query string or path 72 | * component. 73 | */ 74 | 75 | pos = strcspn(n->host, "?/"); 76 | if ('\0' != n->host[pos]) { 77 | if (NULL == (n->path = strdup(&n->host[pos]))) 78 | err(EXIT_FAILURE, NULL); 79 | n->host[pos] = '\0'; 80 | } 81 | 82 | /* 83 | * Do we have HTTP basic authentication enabled? 84 | * If so, we need to reallocate the host to point after the 85 | * authentication bits. 86 | */ 87 | 88 | if (NULL != (cp = strchr(n->host, '@'))) { 89 | httpauth = n->host; 90 | if (NULL == (n->host = strdup(cp + 1))) 91 | err(EXIT_FAILURE, NULL); 92 | *cp = '\0'; 93 | } 94 | 95 | /* If we have a port, it would come after authentication. */ 96 | 97 | if (NULL != (cp = strchr(n->host, ':'))) { 98 | n->addrs.port = strtonum 99 | (cp + 1, 1, SHRT_MAX, &er); 100 | if (NULL != er) 101 | errx(EXIT_FAILURE, "%s: %s: %s", 102 | n->url, cp + 1, er); 103 | *cp = '\0'; 104 | } 105 | 106 | if (NULL == n->path && 107 | NULL == (n->path = strdup("/"))) 108 | err(EXIT_FAILURE, NULL); 109 | 110 | /* If we have HTTP authentication, then base64 encode now. */ 111 | 112 | if (NULL != httpauth) { 113 | srcsz = strlen(httpauth); 114 | targsz = ((srcsz + 2) / 3 * 4) + 1; 115 | if (NULL == (n->httpauth = malloc(targsz))) 116 | err(EXIT_FAILURE, NULL); 117 | c = b64_ntop((const unsigned char *)httpauth, 118 | srcsz, n->httpauth, targsz); 119 | if (c < 0) 120 | errx(EXIT_FAILURE, "b64_ntop"); 121 | free(httpauth); 122 | } 123 | } 124 | 125 | /* 126 | * This is a modified version of host_dns in config.c of OpenBSD's ntpd. 127 | */ 128 | /* 129 | * Copyright (c) 2003, 2004 Henning Brauer 130 | * 131 | * Permission to use, copy, modify, and distribute this software for any 132 | * purpose with or without fee is hereby granted, provided that the above 133 | * copyright notice and this permission notice appear in all copies. 134 | * 135 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 136 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 137 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 138 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 139 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 140 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 141 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 142 | */ 143 | int 144 | dns_resolve(struct out *out, const char *host, struct dns *vec) 145 | { 146 | struct addrinfo hints, *res0, *res; 147 | struct sockaddr *sa; 148 | int error; 149 | 150 | memset(&hints, 0, sizeof(hints)); 151 | hints.ai_family = PF_UNSPEC; 152 | hints.ai_socktype = SOCK_DGRAM; /* DUMMY */ 153 | 154 | error = getaddrinfo(host, NULL, &hints, &res0); 155 | 156 | xdbg(out, "DNS resolving: %s", host); 157 | 158 | if (error == EAI_AGAIN || error == EAI_NONAME) { 159 | xwarnx(out, "DNS resolve error: %s: %s", 160 | host, gai_strerror(error)); 161 | return 0; 162 | } else if (error) { 163 | xwarnx(out, "DNS parse error: %s: %s", 164 | host, gai_strerror(error)); 165 | return 0; 166 | } 167 | 168 | for (vec->addrsz = 0, res = res0; 169 | NULL != res && vec->addrsz < MAX_SERVERS_DNS; 170 | res = res->ai_next) { 171 | if (res->ai_family != AF_INET && 172 | res->ai_family != AF_INET6) 173 | continue; 174 | 175 | sa = res->ai_addr; 176 | if (AF_INET == res->ai_family) { 177 | vec->addrs[vec->addrsz].family = 4; 178 | inet_ntop(AF_INET, 179 | &(((struct sockaddr_in *)sa)->sin_addr), 180 | vec->addrs[vec->addrsz].ip, 181 | INET6_ADDRSTRLEN); 182 | } else { 183 | vec->addrs[vec->addrsz].family = 6; 184 | inet_ntop(AF_INET6, 185 | &(((struct sockaddr_in6 *)sa)->sin6_addr), 186 | vec->addrs[vec->addrsz].ip, 187 | INET6_ADDRSTRLEN); 188 | } 189 | 190 | xdbg(out, "DNS resolved: %s: %s", 191 | host, vec->addrs[vec->addrsz].ip); 192 | vec->addrsz++; 193 | } 194 | 195 | freeaddrinfo(res0); 196 | return 1; 197 | } 198 | -------------------------------------------------------------------------------- /versions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |

0.0.24

6 |
Kristaps Dzonsons
7 | 8 |
9 | 22 |
23 |
24 |
25 |

0.0.21

26 |
Kristaps Dzonsons
27 | 28 |
29 | 34 |
35 |
36 |
37 |

0.0.20

38 |
Kristaps Dzonsons
39 | 40 |
41 | 61 |
62 |
63 |
64 |

0.0.17

65 |
Kristaps Dzonsons
66 | 67 |
68 | 73 |
74 |
75 |
76 |

0.0.16

77 |
Kristaps Dzonsons
78 | 79 |
80 | 100 |
101 |
102 |
103 |

0.0.14

104 |
Kristaps Dzonsons
105 | 106 |
107 | 121 |
122 |
123 |
124 |

0.0.13

125 |
Kristaps Dzonsons
126 | 127 |
128 | 135 |
136 |
137 |
138 |

0.0.12

139 |
Kristaps Dzonsons
140 | 141 |
142 | 148 |
149 |
150 |
151 |

0.0.11

152 |
Kristaps Dzonsons
153 | 154 |
155 | 161 |
162 |
163 |
164 |

0.0.10

165 |
Kristaps Dzonsons
166 | 167 |
168 | 175 |
176 |
177 |
178 |

0.0.9

179 |
Kristaps Dzonsons
180 | 181 |
182 | 187 |
188 |
189 |
190 |

0.0.8

191 |
Kristaps Dzonsons
192 | 193 |
194 | 201 |
202 |
203 | -------------------------------------------------------------------------------- /slant-json.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2018 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #if HAVE_SYS_QUEUE 20 | # include 21 | #endif 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | 34 | #include "extern.h" 35 | #include "slant.h" 36 | #include "json.h" 37 | 38 | /* 39 | * Parse the top-level objects of our JSON body. 40 | * Returns >1 on success, 0 on transient failure (malformatted), <0 on 41 | * system error (the system should halt). 42 | */ 43 | static int 44 | json_parse_obj(struct out *out, const char *str, 45 | const jsmntok_t *t, size_t pos, struct node *n, int toks) 46 | { 47 | int rc = 0; 48 | char *buf; 49 | 50 | if (jsmn_eq(str, &t[pos], "version")) { 51 | if (n->recs->has_version) { 52 | xwarnx(out, "JSON \"version\" " 53 | "duplicated: %s", n->host); 54 | return 0; 55 | } else if (JSMN_STRING != t[++pos].type) { 56 | xwarnx(out, "JSON \"version\" node " 57 | "not a string: %s", n->host); 58 | return 0; 59 | } 60 | n->recs->version = strndup 61 | (str + t[pos].start, 62 | t[pos].end - t[pos].start); 63 | if (NULL == n->recs->version) { 64 | xwarn(out, NULL); 65 | return -1; 66 | } 67 | n->recs->has_version = 1; 68 | return 1; 69 | } else if (jsmn_eq(str, &t[pos], "timestamp")) { 70 | if (n->recs->has_timestamp) { 71 | xwarnx(out, "JSON \"timestamp\" " 72 | "duplicated: %s", n->host); 73 | return 0; 74 | } else if (JSMN_PRIMITIVE != t[++pos].type) { 75 | xwarnx(out, "JSON \"timestamp\" node " 76 | "not a primitive: %s", n->host); 77 | return 0; 78 | } 79 | 80 | /* 81 | * This is clunky. 82 | * Integers are stored as just primitives, so we need to 83 | * parse them out of the stream. 84 | * To do so, convert into a temporary buffer, then 85 | * convert from the buffer. 86 | * To prevent the unlikely event of overflow, we only 87 | * require that ERANGE be satisfied. 88 | */ 89 | 90 | buf = strndup(str + t[pos].start, 91 | t[pos].end - t[pos].start); 92 | if (NULL == buf) { 93 | xwarn(out, NULL); 94 | return -1; 95 | } 96 | errno = 0; 97 | n->recs->timestamp = strtoll(buf, NULL, 10); 98 | if (ERANGE != errno) 99 | n->recs->has_timestamp = 1; 100 | else 101 | xwarnx(out, "JSON \"timestamp\" not " 102 | "a valid number: %s", n->host); 103 | free(buf); 104 | return 1; 105 | } else if (jsmn_eq(str, &t[pos], "system")) { 106 | if (n->recs->has_system) { 107 | xwarnx(out, "JSON \"system\" " 108 | "duplicated: %s", n->host); 109 | return 0; 110 | } 111 | pos++; 112 | rc = jsmn_system(&n->recs->system, 113 | str, &t[pos], toks - pos); 114 | if (0 == rc) 115 | xwarnx(out, "malformed JSON " 116 | "\"system\" node: %s", n->host); 117 | else if (rc < 0) 118 | xwarn(out, NULL); 119 | else 120 | n->recs->has_system = 1; 121 | return rc; 122 | } 123 | 124 | /* Now we do the qmin, min, hour, day, week, and year arrays. */ 125 | 126 | if (jsmn_eq(str, &t[pos], "qmin")) { 127 | if (n->recs->byqminsz) { 128 | xwarnx(out, "JSON \"qmin\" " 129 | "duplicated: %s", n->host); 130 | return 0; 131 | } 132 | pos++; 133 | rc = jsmn_record_array 134 | (&n->recs->byqmin, 135 | &n->recs->byqminsz, 136 | str, &t[pos], toks - pos); 137 | } else if (jsmn_eq(str, &t[pos], "min")) { 138 | if (n->recs->byminsz) { 139 | xwarnx(out, "JSON \"min\" " 140 | "duplicated: %s", n->host); 141 | return 0; 142 | } 143 | pos++; 144 | rc = jsmn_record_array 145 | (&n->recs->bymin, 146 | &n->recs->byminsz, 147 | str, &t[pos], toks - pos); 148 | } else if (jsmn_eq(str, &t[pos], "hour")) { 149 | if (n->recs->byhoursz) { 150 | xwarnx(out, "JSON \"hour\" " 151 | "duplicated: %s", n->host); 152 | return 0; 153 | } 154 | pos++; 155 | rc = jsmn_record_array 156 | (&n->recs->byhour, 157 | &n->recs->byhoursz, 158 | str, &t[pos], toks - pos); 159 | } else if (jsmn_eq(str, &t[pos], "day")) { 160 | if (n->recs->bydaysz) { 161 | xwarnx(out, "JSON \"day\" " 162 | "duplicated: %s", n->host); 163 | return 0; 164 | } 165 | pos++; 166 | rc = jsmn_record_array 167 | (&n->recs->byday, 168 | &n->recs->bydaysz, 169 | str, &t[pos], toks - pos); 170 | } else if (jsmn_eq(str, &t[pos], "week")) { 171 | if (n->recs->byweeksz) { 172 | xwarnx(out, "JSON \"week\" " 173 | "duplicated: %s", n->host); 174 | return 0; 175 | } 176 | pos++; 177 | rc = jsmn_record_array 178 | (&n->recs->byweek, 179 | &n->recs->byweeksz, 180 | str, &t[pos], toks - pos); 181 | } else if (jsmn_eq(str, &t[pos], "year")) { 182 | if (n->recs->byyearsz) { 183 | xwarnx(out, "JSON \"year\" " 184 | "duplicated: %s", n->host); 185 | return 0; 186 | } 187 | pos++; 188 | rc = jsmn_record_array 189 | (&n->recs->byyear, 190 | &n->recs->byyearsz, 191 | str, &t[pos], toks - pos); 192 | } else { 193 | xwarnx(out, "unknown JSON node: %s", n->host); 194 | return 0; 195 | } 196 | 197 | if (0 == rc) 198 | xwarnx(out, "JSON record array node failed: %s", n->host); 199 | else if (rc < 0) 200 | xwarn(out, NULL); 201 | 202 | return rc; 203 | } 204 | 205 | /* 206 | * Parse the full JSON response for a given node. 207 | * We use the JSMN interface produced by kwebapp. 208 | * This is a somewhat... abstruse interface, but simple and robust. 209 | * Returns >0 on success, 0 on transient failure (malformed JSON or 210 | * other recoverable error), <0 on fatal error (system should halt). 211 | */ 212 | int 213 | json_parse(struct out *out, struct node *n, const char *str, size_t sz) 214 | { 215 | int i, toks, ntoks, rc; 216 | size_t j; 217 | jsmn_parser jp; 218 | jsmntok_t *t = NULL; 219 | 220 | /* Allocate results, if necessary, and free existing. */ 221 | 222 | if (NULL == n->recs && 223 | NULL == (n->recs = calloc(1, sizeof(struct recset)))) 224 | goto syserr; 225 | 226 | recset_free(n->recs); 227 | memset(n->recs, 0, sizeof(struct recset)); 228 | 229 | /* Parse to get token length. */ 230 | 231 | jsmn_init(&jp); 232 | if (-1 == (toks = jsmn_parse(&jp, str, sz, NULL, 0))) 233 | goto syserr; 234 | else if (toks <= 0) 235 | goto err; 236 | 237 | /* Allocate tokens and re-parse. */ 238 | 239 | if (NULL == (t = calloc(toks, sizeof(jsmntok_t)))) 240 | goto syserr; 241 | 242 | jsmn_init(&jp); 243 | ntoks = jsmn_parse(&jp, str, sz, t, toks); 244 | 245 | /* 246 | * I'm not sure why this happens, but jsmn_parse() can return a 247 | * different result if "toks" is set. 248 | * Catch that here. 249 | */ 250 | 251 | if (ntoks != toks) { 252 | xwarnx(out, "token count: %d != %d: %s", 253 | ntoks, toks, n->host); 254 | goto err; 255 | } else if (t[0].type != JSMN_OBJECT) { 256 | xwarnx(out, "top-level JSON " 257 | "node not object: %s", n->host); 258 | goto err; 259 | } 260 | 261 | /* Parse each of our top-level objects. */ 262 | 263 | for (i = 0, j = 1; i < t[0].size; i++) { 264 | rc = json_parse_obj 265 | (out, str, &t[j], 0, n, toks - j); 266 | if (rc < 0) 267 | goto syserr; 268 | else if (0 == rc) 269 | goto err; 270 | j += 1 + rc; 271 | } 272 | 273 | free(t); 274 | return 1; 275 | syserr: 276 | xwarn(out, NULL); 277 | free(t); 278 | return -1; 279 | err: 280 | xwarnx(out, "JSON parse: %s", n->host); 281 | fprintf(out->errs, "------>------\n"); 282 | fprintf(out->errs, "%.*s\n", (int)sz, str); 283 | fprintf(out->errs, "------<------\n"); 284 | fflush(out->errs); 285 | free(t); 286 | return 0; 287 | } 288 | 289 | -------------------------------------------------------------------------------- /slant.h: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2018 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #ifndef SLANT_H 18 | #define SLANT_H 19 | 20 | struct source { 21 | int family; /* 4 (PF_INET) or 6 (PF_INET6) */ 22 | char ip[INET6_ADDRSTRLEN]; /* IPV4 or IPV6 address */ 23 | }; 24 | 25 | #define MAX_SERVERS_DNS 8 26 | 27 | struct dns { 28 | size_t addrsz; /* num addrs (<= MAX_SERVERS_DNS) */ 29 | struct source addrs[MAX_SERVERS_DNS]; /* ip addresses */ 30 | short port; /* port */ 31 | int https; /* non-zero if https, else zero */ 32 | size_t curaddr; /* current working address */ 33 | }; 34 | 35 | enum draword { 36 | DRAWORD_CMDLINE = 0, 37 | DRAWORD_CPU, 38 | DRAWORD_HOST, 39 | DRAWORD_MEM, 40 | }; 41 | 42 | enum drawcat { 43 | DRAWCAT_CPU, 44 | DRAWCAT_MEM, 45 | DRAWCAT_NET, 46 | DRAWCAT_DISC, 47 | DRAWCAT_LINK, 48 | DRAWCAT_HOST, 49 | DRAWCAT_PROCS, 50 | DRAWCAT_FILES, 51 | DRAWCAT_RPROCS 52 | }; 53 | 54 | /* 55 | * Within a drawbox, a given line. 56 | * There can be up to six lines per drawing box. 57 | */ 58 | struct drawboxln { 59 | size_t len; /* length */ 60 | size_t lastseen; /* if >0, lastseen time */ 61 | size_t lastrecord; /* if >0, lastrecord time */ 62 | unsigned int line; /* line display bits */ 63 | #define LINE_QMIN 0x0001 64 | #define LINE_MIN 0x0002 65 | #define LINE_HOUR 0x0004 66 | #define LINE_DAY 0x0008 67 | #define LINE_WEEK 0x0010 68 | #define LINE_YEAR 0x0020 69 | #define LINE_QMIN_BARS 0x0100 70 | #define LINE_MIN_BARS 0x0200 71 | #define LINE_HOUR_BARS 0x0400 72 | #define LINE_DAY_BARS 0x0800 73 | #define LINE_WEEK_BARS 0x1000 74 | #define LINE_YEAR_BARS 0x2000 75 | #define LINK_IP 0x0001 76 | #define LINK_STATE 0x0002 77 | #define LINK_ACCESS 0x0004 78 | #define HOST_RECORD 0x0001 79 | #define HOST_SLANT_VERSION 0x0002 80 | #define HOST_UPTIME 0x0004 81 | #define HOST_CLOCK_DRIFT 0x0008 82 | #define HOST_MACHINE 0x0010 83 | #define HOST_OSVERSION 0x0020 84 | #define HOST_OSRELEASE 0x0040 85 | #define HOST_OSSYSNAME 0x0080 86 | }; 87 | 88 | /* 89 | * A box (column) to draw in the output. 90 | * This (currently) can have two lines of content. 91 | */ 92 | struct drawbox { 93 | enum drawcat cat; /* the box category */ 94 | size_t len; /* maximum length for contents */ 95 | struct drawboxln lines[6]; /* our drawables */ 96 | }; 97 | 98 | /* 99 | * We use this structure to keep track of key parts of our UI. 100 | * It lets us optimise repainting the screen per second to keep track of 101 | * our last-seen intervals. 102 | */ 103 | struct draw { 104 | struct drawbox *box; /* all configured boxes */ 105 | size_t boxsz; /* number of boxes */ 106 | int header; /* boolean for header */ 107 | size_t errlog; /* lines in errlog */ 108 | enum draword order; /* order of drawn hosts */ 109 | size_t maxipsz; /* of all IPs possible, length */ 110 | size_t maxhostsz; /* of all hosts possible, length */ 111 | size_t maxmachsz; /* ... machine... */ 112 | size_t maxosversz; /* ... OS versions... */ 113 | size_t maxosrelsz; /* ... OS release... */ 114 | size_t maxosnamesz; /* ... OS sysname... */ 115 | size_t maxline; /* max boxes' nonempty lines */ 116 | }; 117 | 118 | /* 119 | * The full set of records of a particular host. 120 | * This can be totally empty: we have no constraints. 121 | */ 122 | struct recset { 123 | int has_version; 124 | char *version; 125 | int has_timestamp; 126 | int64_t timestamp; 127 | int has_system; 128 | struct system system; 129 | struct record *byqmin; 130 | size_t byqminsz; 131 | struct record *bymin; 132 | size_t byminsz; 133 | struct record *byhour; 134 | size_t byhoursz; 135 | struct record *byday; 136 | size_t bydaysz; 137 | struct record *byweek; 138 | size_t byweeksz; 139 | struct record *byyear; 140 | size_t byyearsz; 141 | }; 142 | 143 | enum state { 144 | STATE_STARTUP = 0, 145 | STATE_RESOLVING, 146 | STATE_CONNECT_WAITING, 147 | STATE_CONNECT_READY, 148 | STATE_CONNECT, 149 | STATE_CLOSE_DONE, 150 | STATE_CLOSE_ERR, 151 | STATE_WRITE, 152 | STATE_READ 153 | }; 154 | 155 | /* 156 | * Data on a current transfer (read/write). 157 | */ 158 | struct xfer { 159 | char *wbuf; /* write buffer for http */ 160 | size_t wbufsz; /* amount left to write */ 161 | size_t wbufpos; /* write position in wbuf */ 162 | char *rbuf; /* read buffer for http */ 163 | size_t rbufsz; /* amount read over http */ 164 | struct sockaddr_storage ss; /* socket */ 165 | struct pollfd *pfd; /* pollfd descriptor */ 166 | struct tls *tls; /* tls context, if needed */ 167 | time_t start; /* connection start time */ 168 | time_t lastio; /* last read/write/connect */ 169 | }; 170 | 171 | /* 172 | * The full status of a single node (URL). 173 | */ 174 | struct node { 175 | enum state state; /* state of node */ 176 | const char *url; /* full URL for connect */ 177 | time_t waittime; /* idle time */ 178 | time_t timeout; /* timeout */ 179 | char *httpauth; /* HTTP basic authenticator or NULL */ 180 | char *host; /* just hostname of connect */ 181 | char *path; /* path of connect */ 182 | struct xfer xfer; /* transfer information */ 183 | struct dns addrs; /* all possible IP addresses */ 184 | time_t waitstart; /* wait period start */ 185 | time_t lastseen; /* last sample data received */ 186 | time_t drift; /* time drift or zero (valid drift!) */ 187 | struct recset *recs; /* results */ 188 | int dirty; /* new results */ 189 | }; 190 | 191 | /* 192 | * Per-node configuration. 193 | */ 194 | struct nconfig { 195 | char *url; /* URL */ 196 | time_t waittime; /* idle time (or zero) */ 197 | time_t timeout; /* timeout (or zero) */ 198 | }; 199 | 200 | /* 201 | * A parsed configuration file (~/.slantrc or similar). 202 | */ 203 | struct config { 204 | struct draw *draw; /* draw config or NULL */ 205 | struct nconfig *urls; /* nodes (URLs) */ 206 | size_t urlsz; /* number of urls */ 207 | size_t waittime; /* global idle time */ 208 | size_t timeout; /* global timeout */ 209 | }; 210 | 211 | /* 212 | * Output information (window, etc.). 213 | */ 214 | struct out { 215 | WINDOW *errwin; /* error panel */ 216 | WINDOW *mainwin; /* main panel */ 217 | FILE *errs; /* output error file */ 218 | int debug; /* print debugging if non-zero */ 219 | }; 220 | 221 | __BEGIN_DECLS 222 | 223 | void xdbg(struct out *, const char *, ...) 224 | __attribute__((format(printf, 2, 3))); 225 | void xwarnx(struct out *, const char *, ...) 226 | __attribute__((format(printf, 2, 3))); 227 | void xwarn(struct out *, const char *, ...) 228 | __attribute__((format(printf, 2, 3))); 229 | 230 | size_t compute_width(const struct node *, size_t, 231 | const struct draw *); 232 | 233 | void dns_parse_url(struct out *, struct node *); 234 | int dns_resolve(struct out *, const char *, struct dns *); 235 | 236 | int http_init_connect(struct out *, struct node *, time_t); 237 | int http_close_done(struct out *, struct node *, time_t); 238 | int http_close_err(struct out *, struct node *, time_t); 239 | int http_connect(struct out *, struct node *, time_t); 240 | int http_write(struct out *, struct node *, time_t); 241 | int http_read(struct out *, struct node *, time_t); 242 | 243 | void draw(struct out *, struct draw *, 244 | const struct node *, size_t, time_t); 245 | void drawtimes(struct out *, const struct draw *, 246 | const struct node *, size_t, time_t); 247 | 248 | int json_parse(struct out *, struct node *n, const char *, size_t); 249 | 250 | void recset_free(struct recset *); 251 | 252 | int config_parse(const char *, struct config *, int, char *[]); 253 | void config_free(struct config *); 254 | 255 | __END_DECLS 256 | 257 | #endif /* SLANT_H */ 258 | -------------------------------------------------------------------------------- /index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | slant: remote system monitor 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

slant

23 |
a BSD.lv project by CAPEM
24 |
25 | 32 |
33 |
34 |
35 |
36 | a minimal open source system monitor for remote UNIX machines 37 |
38 |
39 | 42 |
43 |
44 | 48 | 52 | 56 |
57 |

overview

58 |

59 | Slant is an open 61 | source, minimal remote system monitor. 62 | It models host fitness via a set of numeric quantifiers, for 63 | instance, CPU usage quantified as a percent average over all CPUs, 64 | memory usage over available memory, number of processes over maximum 65 | configured, etc. 66 | For past behaviour, slant maintains a set of 67 | time-interval-bound circular queues (hourly, daily, etc.) with 68 | current data aggregated into each time series. 69 | An important design decision of slant is 70 | that each monitoring host retains its own [bounded] history. 71 |

72 |

73 | Slant is not designed for 74 | detailed analysis: there are plenty of other tools for that. 75 | (Monit, 76 | zabbix, etc.) 77 |

78 |

79 | The current version of slant has a single 80 | graphing front-end inspired by top. 81 | The goal of this tool is to answer, at a single glance, the question 82 | of has anything gone to hell with my systems. 83 |

84 |

operation

85 |

86 | Slant currently runs on OpenBSD only. It's 88 | currently being ported to some other UNIX (especially BSD) systems. 89 | The source code is in clean, well-documented C code. 90 |

91 |

92 | There are two main components of the slant 93 | system. 94 | The first is the server; the second is the client. 95 |

96 |
97 | 98 |
99 |

100 | The server is divided into a collector, slant-collectd(8), which 102 | collects system statistics and writes to a database; and a CGI 103 | script, slant-cgi(8), which exports 104 | the database to clients. 105 | The collector is a simple daemon that interacts with an SQLite database via sqlbox. 108 | The database is well-documented and consists of a set of circular 109 | buffers for accumulating historical data. 110 | (There is also an instantaneous buffer, which averages only 111 | the last entry.) 112 | The database is in practise bounded in size, seeming to average in 113 | size to 50K. 114 | The full transmitted JSON output is about 100K, compressed 17K. 115 | The JSON is well-documented in slant-cgi(8). 117 |

118 |

119 | The current client, slant(1), is a 120 | fully-configurable ncurses system. 122 | It has simple defaults for laying out monitored data (according to 123 | screen size), and all output may be customised in the configuration 124 | file. 125 | The utility and its configuration are well-documented in slant(1). 127 |

128 |

data

129 |

130 | Slant collects enough data to minimally represent system health. 131 | A strong focus is on data being finite and bounded: many other 132 | system monitors collect and store vast amounts of information. 133 | A design goal of slant is to be small and light. 134 | For the time being, the following are present as both instantaneous and past averages: 135 |

136 |
    137 |
  • 138 | processor: 139 | percentage of non-idle processor time averaged over all processing units 140 |
  • 141 |
  • 142 | memory: 143 | percentage of active over available memory 144 |
  • 145 |
  • 146 | network: 147 | inbound and outbound network traffic averaged over configured interfaces 148 |
  • 149 |
  • 150 | disc: 151 | reads and writes averaged over configured interfaces 152 |
  • 153 |
  • 154 | processes: 155 | percentage of running over possible processes 156 |
  • 157 |
  • 158 | runnables: 159 | percentage of configured processes (e.g., sshd) currently running 160 |
  • 161 |
  • 162 | files: 163 | percentage of used over available open files 164 |
  • 165 |
166 |

167 | Beyond numerical (aggregatable) data, slant also records the system boot time, operating 169 | system information (architecture, version, release), and timestamp for 170 | computing clock drift. 171 |

172 |

173 | Data represented by slant has three types. 174 | The first is aggregatable and always numerical. 175 | This, like CPU percentage, may be aggregated over time intervals for 176 | examining historical average records. 177 | The second is start-time data, which is set when the collector daemon starts. 178 | This is hardware configuration, system boot information, collector version, etc. 179 | The last is non-aggregatable instantaneous information, for example, 180 | a list of all processes. 181 | At this time, slant collects no 182 | instantaneous and little start-time data. 183 | This is intentional: the focus is on aggregatables. 184 |

185 |

download

186 |

187 | The current version is always available at snapshots/slant.tar.gz. 189 | Just unpack the source, configure with ./configure, then 190 | run make. 191 | Lastly, run make install as root. 192 | Then make sure the collector, slant-collectd is running 194 | and slowcgi (for 195 | OpenBSD) is properly configured. 196 | You'll need openradtool 198 | to build the system. 199 |

200 |

201 | For upgrading the database between versions or installing the 202 | initial database, make sure to run slant-upgrade. 204 | Make sure that the CGI script isn't accessable before you do so, or 205 | you may have new CGI sources and an old database schema. 206 |

207 |

news

208 | 216 |

about

217 |

218 | Slant was built to provide simple monitoring of a set of systems. 219 | The operative term being simple, both to deploy and to actually monitor. 220 | Development is generously sponsored by CAPEM Solutions, Inc.—thank you! 221 |

222 |
223 |
224 |
225 | © 2018–2020, CAPEM Solutions Inc. 226 |
227 |
228 | 229 | 230 | -------------------------------------------------------------------------------- /tests.c: -------------------------------------------------------------------------------- 1 | #if TEST___PROGNAME 2 | int 3 | main(void) 4 | { 5 | extern char *__progname; 6 | 7 | return !__progname; 8 | } 9 | #endif /* TEST___PROGNAME */ 10 | #if TEST_ARC4RANDOM 11 | #include 12 | 13 | int 14 | main(void) 15 | { 16 | return (arc4random() + 1) ? 0 : 1; 17 | } 18 | #endif /* TEST_ARC4RANDOM */ 19 | #if TEST_B64_NTOP 20 | #include 21 | #include 22 | 23 | int 24 | main(void) 25 | { 26 | const char *src = "hello world"; 27 | char output[1024]; 28 | 29 | return b64_ntop((const unsigned char *)src, 11, output, sizeof(output)) > 0 ? 0 : 1; 30 | } 31 | #endif /* TEST_B64_NTOP */ 32 | #if TEST_CAPSICUM 33 | #include 34 | 35 | int 36 | main(void) 37 | { 38 | cap_enter(); 39 | return(0); 40 | } 41 | #endif /* TEST_CAPSICUM */ 42 | #if TEST_ENDIAN_H 43 | #include 44 | 45 | int 46 | main(void) 47 | { 48 | return !htole32(23); 49 | } 50 | #endif /* TEST_ENDIAN_H */ 51 | #if TEST_ERR 52 | /* 53 | * Copyright (c) 2015 Ingo Schwarze 54 | * 55 | * Permission to use, copy, modify, and distribute this software for any 56 | * purpose with or without fee is hereby granted, provided that the above 57 | * copyright notice and this permission notice appear in all copies. 58 | * 59 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 60 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 61 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 62 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 63 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 64 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 65 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 66 | */ 67 | 68 | #include 69 | 70 | int 71 | main(void) 72 | { 73 | warnx("%d. warnx", 1); 74 | warn("%d. warn", 2); 75 | err(0, "%d. err", 3); 76 | /* NOTREACHED */ 77 | return 1; 78 | } 79 | #endif /* TEST_ERR */ 80 | #if TEST_EXPLICIT_BZERO 81 | #include 82 | 83 | int 84 | main(void) 85 | { 86 | char foo[10]; 87 | 88 | explicit_bzero(foo, sizeof(foo)); 89 | return(0); 90 | } 91 | #endif /* TEST_EXPLICIT_BZERO */ 92 | #if TEST_GETEXECNAME 93 | #include 94 | 95 | int 96 | main(void) 97 | { 98 | const char * progname; 99 | 100 | progname = getexecname(); 101 | return progname == NULL; 102 | } 103 | #endif /* TEST_GETEXECNAME */ 104 | #if TEST_GETPROGNAME 105 | #include 106 | 107 | int 108 | main(void) 109 | { 110 | const char * progname; 111 | 112 | progname = getprogname(); 113 | return progname == NULL; 114 | } 115 | #endif /* TEST_GETPROGNAME */ 116 | #if TEST_INFTIM 117 | /* 118 | * Linux doesn't (always?) have this. 119 | */ 120 | 121 | #include 122 | #include 123 | 124 | int 125 | main(void) 126 | { 127 | printf("INFTIM is defined to be %ld\n", (long)INFTIM); 128 | return 0; 129 | } 130 | #endif /* TEST_INFTIM */ 131 | #if TEST_MD5 132 | #include 133 | #include 134 | 135 | int main(void) 136 | { 137 | MD5_CTX ctx; 138 | 139 | MD5Init(&ctx); 140 | MD5Update(&ctx, "abcd", 4); 141 | 142 | return 0; 143 | } 144 | #endif /* TEST_MD5 */ 145 | #if TEST_MEMMEM 146 | #define _GNU_SOURCE 147 | #include 148 | 149 | int 150 | main(void) 151 | { 152 | char *a = memmem("hello, world", strlen("hello, world"), "world", strlen("world")); 153 | return(NULL == a); 154 | } 155 | #endif /* TEST_MEMMEM */ 156 | #if TEST_MEMRCHR 157 | #if defined(__linux__) || defined(__MINT__) 158 | #define _GNU_SOURCE /* See test-*.c what needs this. */ 159 | #endif 160 | #include 161 | 162 | int 163 | main(void) 164 | { 165 | const char *buf = "abcdef"; 166 | void *res; 167 | 168 | res = memrchr(buf, 'a', strlen(buf)); 169 | return(NULL == res ? 1 : 0); 170 | } 171 | #endif /* TEST_MEMRCHR */ 172 | #if TEST_MEMSET_S 173 | #include 174 | 175 | int main(void) 176 | { 177 | char buf[10]; 178 | memset_s(buf, 0, 'c', sizeof(buf)); 179 | return 0; 180 | } 181 | #endif /* TEST_MEMSET_S */ 182 | #if TEST_PATH_MAX 183 | /* 184 | * POSIX allows PATH_MAX to not be defined, see 185 | * http://pubs.opengroup.org/onlinepubs/9699919799/functions/sysconf.html; 186 | * the GNU Hurd is an example of a system not having it. 187 | * 188 | * Arguably, it would be better to test sysconf(_SC_PATH_MAX), 189 | * but since the individual *.c files include "config.h" before 190 | * , overriding an excessive value of PATH_MAX from 191 | * "config.h" is impossible anyway, so for now, the simplest 192 | * fix is to provide a value only on systems not having any. 193 | * So far, we encountered no system defining PATH_MAX to an 194 | * impractically large value, even though POSIX explicitly 195 | * allows that. 196 | * 197 | * The real fix would be to replace all static buffers of size 198 | * PATH_MAX by dynamically allocated buffers. But that is 199 | * somewhat intrusive because it touches several files and 200 | * because it requires changing struct mlink in mandocdb.c. 201 | * So i'm postponing that for now. 202 | */ 203 | 204 | #include 205 | #include 206 | 207 | int 208 | main(void) 209 | { 210 | printf("PATH_MAX is defined to be %ld\n", (long)PATH_MAX); 211 | return 0; 212 | } 213 | #endif /* TEST_PATH_MAX */ 214 | #if TEST_PLEDGE 215 | #include 216 | 217 | int 218 | main(void) 219 | { 220 | return !!pledge("stdio", NULL); 221 | } 222 | #endif /* TEST_PLEDGE */ 223 | #if TEST_PROGRAM_INVOCATION_SHORT_NAME 224 | #define _GNU_SOURCE /* See feature_test_macros(7) */ 225 | #include 226 | 227 | int 228 | main(void) 229 | { 230 | 231 | return !program_invocation_short_name; 232 | } 233 | #endif /* TEST_PROGRAM_INVOCATION_SHORT_NAME */ 234 | #if TEST_READPASSPHRASE 235 | #include 236 | #include 237 | 238 | int 239 | main(void) 240 | { 241 | return !!readpassphrase("prompt: ", NULL, 0, 0); 242 | } 243 | #endif /* TEST_READPASSPHRASE */ 244 | #if TEST_REALLOCARRAY 245 | #include 246 | 247 | int 248 | main(void) 249 | { 250 | return !reallocarray(NULL, 2, 2); 251 | } 252 | #endif /* TEST_REALLOCARRAY */ 253 | #if TEST_RECALLOCARRAY 254 | #include 255 | 256 | int 257 | main(void) 258 | { 259 | return !recallocarray(NULL, 0, 2, 2); 260 | } 261 | #endif /* TEST_RECALLOCARRAY */ 262 | #if TEST_SANDBOX_INIT 263 | #include 264 | 265 | int 266 | main(void) 267 | { 268 | char *ep; 269 | int rc; 270 | 271 | rc = sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, &ep); 272 | if (-1 == rc) 273 | sandbox_free_error(ep); 274 | return(-1 == rc); 275 | } 276 | #endif /* TEST_SANDBOX_INIT */ 277 | #if TEST_SECCOMP_FILTER 278 | #include 279 | #include 280 | #include 281 | 282 | int 283 | main(void) 284 | { 285 | 286 | prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, 0); 287 | return(EFAULT == errno ? 0 : 1); 288 | } 289 | #endif /* TEST_SECCOMP_FILTER */ 290 | #if TEST_SOCK_NONBLOCK 291 | /* 292 | * Linux doesn't (always?) have this. 293 | */ 294 | 295 | #include 296 | 297 | int 298 | main(void) 299 | { 300 | int fd[2]; 301 | socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, 0, fd); 302 | return 0; 303 | } 304 | #endif /* TEST_SOCK_NONBLOCK */ 305 | #if TEST_STRLCAT 306 | #include 307 | 308 | int 309 | main(void) 310 | { 311 | char buf[3] = "a"; 312 | return ! (strlcat(buf, "b", sizeof(buf)) == 2 && 313 | buf[0] == 'a' && buf[1] == 'b' && buf[2] == '\0'); 314 | } 315 | #endif /* TEST_STRLCAT */ 316 | #if TEST_STRLCPY 317 | #include 318 | 319 | int 320 | main(void) 321 | { 322 | char buf[2] = ""; 323 | return ! (strlcpy(buf, "a", sizeof(buf)) == 1 && 324 | buf[0] == 'a' && buf[1] == '\0'); 325 | } 326 | #endif /* TEST_STRLCPY */ 327 | #if TEST_STRNDUP 328 | #include 329 | 330 | int 331 | main(void) 332 | { 333 | const char *foo = "bar"; 334 | char *baz; 335 | 336 | baz = strndup(foo, 1); 337 | return(0 != strcmp(baz, "b")); 338 | } 339 | #endif /* TEST_STRNDUP */ 340 | #if TEST_STRNLEN 341 | #include 342 | 343 | int 344 | main(void) 345 | { 346 | const char *foo = "bar"; 347 | size_t sz; 348 | 349 | sz = strnlen(foo, 1); 350 | return(1 != sz); 351 | } 352 | #endif /* TEST_STRNLEN */ 353 | #if TEST_STRTONUM 354 | /* 355 | * Copyright (c) 2015 Ingo Schwarze 356 | * 357 | * Permission to use, copy, modify, and distribute this software for any 358 | * purpose with or without fee is hereby granted, provided that the above 359 | * copyright notice and this permission notice appear in all copies. 360 | * 361 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 362 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 363 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 364 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 365 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 366 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 367 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 368 | */ 369 | 370 | #include 371 | 372 | int 373 | main(void) 374 | { 375 | const char *errstr; 376 | 377 | if (strtonum("1", 0, 2, &errstr) != 1) 378 | return 1; 379 | if (errstr != NULL) 380 | return 2; 381 | if (strtonum("1x", 0, 2, &errstr) != 0) 382 | return 3; 383 | if (errstr == NULL) 384 | return 4; 385 | if (strtonum("2", 0, 1, &errstr) != 0) 386 | return 5; 387 | if (errstr == NULL) 388 | return 6; 389 | if (strtonum("0", 1, 2, &errstr) != 0) 390 | return 7; 391 | if (errstr == NULL) 392 | return 8; 393 | return 0; 394 | } 395 | #endif /* TEST_STRTONUM */ 396 | #if TEST_SYS_QUEUE 397 | #include 398 | #include 399 | 400 | struct foo { 401 | int bar; 402 | TAILQ_ENTRY(foo) entries; 403 | }; 404 | 405 | TAILQ_HEAD(fooq, foo); 406 | 407 | int 408 | main(void) 409 | { 410 | struct fooq foo_q; 411 | struct foo *p, *tmp; 412 | int i = 0; 413 | 414 | TAILQ_INIT(&foo_q); 415 | 416 | /* 417 | * Use TAILQ_FOREACH_SAFE because some systems (e.g., Linux) 418 | * have TAILQ_FOREACH but not the safe variant. 419 | */ 420 | 421 | TAILQ_FOREACH_SAFE(p, &foo_q, entries, tmp) 422 | p->bar = i++; 423 | return 0; 424 | } 425 | #endif /* TEST_SYS_QUEUE */ 426 | #if TEST_SYS_TREE 427 | #include 428 | #include 429 | 430 | struct node { 431 | RB_ENTRY(node) entry; 432 | int i; 433 | }; 434 | 435 | static int 436 | intcmp(struct node *e1, struct node *e2) 437 | { 438 | return (e1->i < e2->i ? -1 : e1->i > e2->i); 439 | } 440 | 441 | RB_HEAD(inttree, node) head = RB_INITIALIZER(&head); 442 | RB_PROTOTYPE(inttree, node, entry, intcmp) 443 | RB_GENERATE(inttree, node, entry, intcmp) 444 | 445 | int testdata[] = { 446 | 20, 16, 17, 13, 3, 6, 1, 8, 2, 4 447 | }; 448 | 449 | int 450 | main(void) 451 | { 452 | size_t i; 453 | struct node *n; 454 | 455 | for (i = 0; i < sizeof(testdata) / sizeof(testdata[0]); i++) { 456 | if ((n = malloc(sizeof(struct node))) == NULL) 457 | return 1; 458 | n->i = testdata[i]; 459 | RB_INSERT(inttree, &head, n); 460 | } 461 | 462 | return 0; 463 | } 464 | 465 | #endif /* TEST_SYS_TREE */ 466 | #if TEST_SYSTRACE 467 | #include 468 | #include 469 | 470 | #include 471 | 472 | int 473 | main(void) 474 | { 475 | 476 | return(0); 477 | } 478 | #endif /* TEST_SYSTRACE */ 479 | #if TEST_UNVEIL 480 | #include 481 | 482 | int 483 | main(void) 484 | { 485 | return -1 != unveil(NULL, NULL); 486 | } 487 | #endif /* TEST_UNVEIL */ 488 | #if TEST_ZLIB 489 | #include 490 | #include 491 | 492 | int 493 | main(void) 494 | { 495 | gzFile gz; 496 | 497 | if (NULL == (gz = gzopen("/dev/null", "w"))) 498 | return(1); 499 | gzputs(gz, "foo"); 500 | gzclose(gz); 501 | return(0); 502 | } 503 | #endif /* TEST_ZLIB */ 504 | -------------------------------------------------------------------------------- /slant.1: -------------------------------------------------------------------------------- 1 | .\" $Id$ 2 | .\" 3 | .\" Copyright (c) 2018 Kristaps Dzonsons 4 | .\" 5 | .\" Permission to use, copy, modify, and distribute this software for any 6 | .\" purpose with or without fee is hereby granted, provided that the above 7 | .\" copyright notice and this permission notice appear in all copies. 8 | .\" 9 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | .\" 17 | .Dd $Mdocdate$ 18 | .Dt SLANT 1 19 | .Os 20 | .Sh NAME 21 | .Nm slant 22 | .Nd client for remote system monitoring 23 | .Sh SYNOPSIS 24 | .Nm slant 25 | .Op Fl f Ar config 26 | .Op Fl o Ar order 27 | .Op Ar url... 28 | .Sh DESCRIPTION 29 | The 30 | .Nm 31 | utility displays system statistics gathered from resources listed in 32 | .Pa ~/.slantrc . 33 | These resource must be running 34 | .Xr slant-cgi 8 . 35 | Its arguments are as follows: 36 | .Bl -tag -width Ds 37 | .It Fl f Ar config 38 | Specify an alternate configuration location. 39 | .It Fl o Ar order 40 | Default order of host listing. 41 | May be 42 | .Ar cmdline 43 | for natural order, 44 | .Ar host 45 | for hostname, 46 | .Ar cpu 47 | for immediate CPU, or 48 | .Ar mem 49 | for immediate memory. 50 | .It Ar url 51 | Override the configuration's hosts with those provided. 52 | See 53 | .Sx URLs 54 | for details. 55 | .El 56 | .Pp 57 | By default, hosts are ordered as given in the configuration and are 58 | queried every 60 seconds. 59 | If hosts are passed as arguments to 60 | .Nm , 61 | they are used instead of the configuration file's. 62 | .Pp 63 | If not overridden in the configuration, host status is displayed as 64 | if given the following configuration: 65 | .Bd -literal -offset indent 66 | layout { 67 | header ; 68 | errlog 10 ; 69 | host { 70 | cpu qmin_bars qmin hour ; 71 | mem qmin_bars qmin hour ; 72 | nprocs qmin_bars qmin hour ; 73 | net qmin hour ; 74 | disc qmin hour ; 75 | link ip state access ; 76 | rprocs qmin ; 77 | host record ; 78 | } ; 79 | } ; 80 | .Ed 81 | .Pp 82 | .Nm 83 | needs a minimum of about 90 columns to display data, plus more depending 84 | upon the length of the domain names. 85 | If the window isn't wide enough, the following are elided until the 86 | minimum size is reached: 87 | .Pp 88 | .Bl -enum -compact 89 | .It 90 | bar charts 91 | .It 92 | hourly records 93 | .It 94 | link IP and state 95 | .El 96 | .Pp 97 | Data is not recorded to the local machine: it is 98 | .Qq live . 99 | So if the front-end is shut down and restarted with hosts that are 100 | unresponsive, their data will not be refreshed til the next access. 101 | .Pp 102 | An error and debug log is shown below the table of all hosts. 103 | The log is saved to 104 | .Pa ~/.slant-errlog . 105 | .Ss Configuration file 106 | The configuration file, which defaults to 107 | .Pa ~/.slantrc , 108 | is structured with the following top-level nodes. 109 | All tokens are white-space separated, so 110 | .Qq servers foo; 111 | species a server named 112 | .Qq foo; . 113 | Literals are case sensitive. 114 | .Bd -literal -offset indent 115 | "waittime" NUM ";" 116 | "timeout" NUM ";" 117 | .Ed 118 | .Pp 119 | The global 120 | .Li waittime 121 | before each host is processed. 122 | The countdown for each host begins after its last disconnect. 123 | The minimum is 15 secons. 124 | Also the global 125 | .Li timeout 126 | after which data transfers are terminated (following the last byte 127 | transferred). 128 | The minimum is 10 seconds. 129 | .Bd -literal -offset indent 130 | "servers" url [url...] ["{" 131 | ["waittime" NUM ";" ] 132 | ["timeout" NUM ";" ] 133 | "}"] ";" 134 | .Ed 135 | .Pp 136 | There may be several 137 | .Cm servers 138 | sections, each with different server arguments overriding the globals. 139 | See 140 | .Sx URLs 141 | for details on the 142 | .Li url 143 | field. 144 | .Bd -literal -offset indent 145 | "layout" "{" 146 | ["header" ";"] 147 | ["host" "{" [column]+ "}" ";" 148 | ["errlog" NUM ";"] 149 | "}" ";" 150 | .Ed 151 | .Pp 152 | The 153 | .Cm layout 154 | block configures how host are displayed. 155 | If 156 | .Cm header 157 | is specified, a column header is shown at the top of the screen. 158 | If 159 | .Cm errlog 160 | is non-zero, it is the rows in the error/debug window. 161 | The 162 | .Cm host 163 | block consists of a series of column types and arguments. 164 | Each column may have up to six lines of content. 165 | If the column arguments do not have a line specification, they're 166 | assumed to be on the first line. 167 | In the event of duplicate lines, only the last is retained. 168 | A column for a host ends in a semicolon or the close-brace for the host 169 | layout block. 170 | .Bd -literal -offset indent 171 | "host" "{" 172 | [ coltype 173 | [ "line1" "{" colargs "}" | 174 | "line2" "{" colargs "}" | 175 | "line3" "{" colargs "}" | 176 | "line4" "{" colargs "}" | 177 | "line5" "{" colargs "}" | 178 | "line6" "{" colargs "}" | 179 | colargs 180 | ] ";" ]+ 181 | "}" ";"] 182 | .Ed 183 | .Pp 184 | The column types and arguments are as follows: 185 | .Bd -literal -offset indent 186 | "cpu" [time_interval_bars|time_interval]+ 187 | "mem" [time_interval_bars|time_interval]+ 188 | "net" [time_interval]+ 189 | "disc" [time_interval]+ 190 | "link" ["ip"|"state"|"access"]+ 191 | "host" ["record"|"slant_version"|"uptime"|"clock_drift"|uname]+ 192 | "nprocs" [time_interval_bars|time_interval]+ 193 | "rprocs" [time_interval_bars|time_interval]+ 194 | "nfiles" [time_interval_bars|time_interval]+ 195 | .Ed 196 | .Pp 197 | The 198 | .Cm time_interval_bars 199 | fields draw coloured bar graph when specifying 200 | .Cm qmin_bars , 201 | for the quarter-minute summary; 202 | .Cm min_bars , 203 | the minute summary; 204 | .Cm hour_bars , 205 | the hourly summary; 206 | .Cm day_bars , 207 | the daily summary; 208 | .Cm week_bars , 209 | the weekly summary; and 210 | .Cm year_bars , 211 | the yearly summary. 212 | The 213 | .Cm time_interval 214 | writes a percentage when specifying 215 | .Cm qmin , 216 | for the quarter-minute summary; 217 | .Cm min , 218 | for the minute summary; 219 | .Cm hour , 220 | for the hourly summary; 221 | .Cm day , 222 | for the daily summary; 223 | .Cm week , 224 | for the weekly summary; or 225 | .Cm year , 226 | for the yearly summary. 227 | These summaries depend upon the category. 228 | .Pp 229 | The 230 | .Cm uname 231 | fields may be 232 | .Cm machine , 233 | for host hardware; 234 | .Cm osversion , 235 | for OS-specific version; 236 | .Cm osrelease , 237 | for OS-specific release; and 238 | .Cm osname , 239 | for operating system name. 240 | .Bl -tag -width Ds 241 | .It Cm cpu 242 | CPU time averaged across all cores/CPUs on the system. 243 | Summaries are shown as percentages. 244 | Percentages more than 80% are coloured red; more than 50%, yellow. 245 | The bar graph of the instantaneous view coloured in the same way. 246 | .It Cm mem 247 | Memory usage (all active memory over all pages). 248 | Summaries are shown as percentage. 249 | Percentages more than 80% are coloured red; more than 50%, yellow. 250 | The bar graph of the instantaneous view coloured in the same way. 251 | .It Cm net 252 | Data received and transmitted as averaged over all network devices. 253 | Summaries are in human-readable scaled units (e.g., KB/s). 254 | .It Cm disc 255 | Data read and written as averaged over all configured devices. 256 | This is shown as an average over two intervals: in the last 15 257 | Summaries are in human-readable scaled units (e.g., KB/s). 258 | .It Cm link 259 | If requesting 260 | .Cm ip , 261 | the IP address (IPV4 or IPV6) of the host and the connection state. 262 | If 263 | .Cm state , 264 | may be one of 265 | .Li strt , 266 | startup; 267 | .Li rslv , 268 | resolving; 269 | .Li idle , 270 | waiting for next connection; 271 | .Li cnrd , 272 | ready to connect; 273 | .Li cnct , 274 | connecting; 275 | .Li cldn , 276 | connection finished (success); 277 | .Li cler , 278 | connection finished (error); 279 | .Li wrte , 280 | writing request; or 281 | .Li read , 282 | reading response. 283 | Lastly, 284 | .Cm access 285 | is the time since last ping. 286 | Shown as hours, minutes, seconds elapsed. 287 | If a worrying amount of elapsed time has shown, the time will be shown 288 | in yellow. 289 | If the amount indicates problems, it will be shown in red. 290 | .It Cm host 291 | If 292 | .Cm record , 293 | the last data collection time as recorded by the remote host's 294 | collection system. 295 | Shown as hours, minutes, seconds elapsed. 296 | If a worrying amount of elapsed time has shown, the time will be shown 297 | in yellow. 298 | If the amount indicates problems, it will be shown in red. 299 | The last connection time as recorded by the local host's 300 | Shown as hours, minutes, seconds elapsed. 301 | If a worrying amount of elapsed time has shown, the time will be shown 302 | in yellow. 303 | If the amount indicates problems, it will be shown in red. 304 | The 305 | .Cm slant_version , 306 | column argument prints the collector version. 307 | Lastly, 308 | .Cm uptime 309 | prints the uptime in days, hours, and minutes. 310 | .It Cm nprocs 311 | The number of running processes over the maximum configured amount. 312 | Summaries are in percentages. 313 | Percentages more than 80% are coloured red; more than 50%, yellow. 314 | The bar graph of the instantaneous view is coloured in the same way. 315 | .It Cm rprocs 316 | Of the commands given to the collector for monitoring, the percentage 317 | that are running. 318 | Summaries are in percentages. 319 | If no commands were given, is always 100%. 320 | If less than 100%, shown in red. 321 | .It Cm nfiles 322 | The number of open files over the maximum possible amount. 323 | Summaries are in percentages. 324 | Percentages more than 80% are coloured red; more than 50%, yellow. 325 | The bar graph of the instantaneous view is coloured in the same way. 326 | .El 327 | .Pp 328 | The hostname (domain name) is always shown first. 329 | .Ss URLs 330 | The URLs passed on the command line or in the configuration file are in 331 | the following format: 332 | .Bd -literal 333 | [https://|http://][username:password@]host[:port][/path][?query] 334 | .Ed 335 | .Pp 336 | If the schema is not provided, HTTP is used regardless of the port (if 337 | given). 338 | The username and password should only be used with HTTPS, but this is 339 | not mandated. 340 | .\" The following requests should be uncommented and used where appropriate. 341 | .\" .Sh CONTEXT 342 | .\" For section 9 functions only. 343 | .\" .Sh RETURN VALUES 344 | .\" For sections 2, 3, and 9 function return values only. 345 | .\" .Sh ENVIRONMENT 346 | .\" For sections 1, 6, 7, and 8 only. 347 | .\" .Sh FILES 348 | .\" .Sh EXIT STATUS 349 | .\" For sections 1, 6, and 8 only. 350 | .Sh EXAMPLES 351 | To query the localhost and a remote machine: 352 | .Bd -literal 353 | % slant -o host 354 | .Ed 355 | .Pp 356 | With the following configuration file: 357 | .Bd -literal -offset indent 358 | waittime 60 ; 359 | servers 360 | https://remote/slant-cgi 361 | ; 362 | servers 363 | localhost/cgi-bin/slant-cgi 364 | { waittime 15 } ; 365 | .Ed 366 | .Pp 367 | This establishes a default wait time of 60 seconds. 368 | This value is used by the remote host. 369 | The local host has its wait time overriden at 15 seconds. 370 | It uses the default node display. 371 | .Pp 372 | The following is optimised for a quick look at memory and CPU usage of 373 | many hosts, so the error log and header are suppressed. 374 | .Bd -literal -offset indent 375 | waittime 60 ; 376 | servers 377 | https://remote1/slant-cgi 378 | https://remote2/slant-cgi 379 | https://remote3/slant-cgi 380 | ; 381 | servers 382 | localhost/cgi-bin/slant-cgi 383 | { waittime 15 } ; 384 | layout { 385 | host { 386 | cpu qmin_bars qmin min hour day ; 387 | mem qmin_bars qmin min hour day ; 388 | link access ; 389 | } 390 | } ; 391 | .Ed 392 | .Pp 393 | A more vertical layout for the same is as follows. 394 | This stacks information using the multi-line features. 395 | .Bd -literal -offset indent 396 | layout { 397 | host { 398 | cpu 399 | line1 { qmin_bars qmin } 400 | line2 { min_bars min } 401 | line3 { hour_bars hour } 402 | line4 { day_bars day } 403 | line5 { week_bars week } 404 | line6 { year_bars year } ; 405 | mem 406 | line1 { qmin_bars qmin } 407 | line2 { min_bars min } 408 | line3 { hour_bars hour } 409 | line4 { day_bars day } 410 | line5 { week_bars week } 411 | line6 { year_bars year } ; 412 | link 413 | line1 { access state } 414 | line2 { ip } ; 415 | } 416 | } ; 417 | .Ed 418 | .\" .Sh DIAGNOSTICS 419 | .\" For sections 1, 4, 6, 7, 8, and 9 printf/stderr messages only. 420 | .\" .Sh ERRORS 421 | .\" For sections 2, 3, 4, and 9 errno settings only. 422 | .Sh SEE ALSO 423 | .Xr slant-collectd 8 424 | .\" .Sh STANDARDS 425 | .\" .Sh HISTORY 426 | .\" .Sh AUTHORS 427 | .\" .Sh CAVEATS 428 | .\" .Sh BUGS 429 | -------------------------------------------------------------------------------- /slant-collectd.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2018 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #if HAVE_SYS_QUEUE 20 | # include 21 | #endif 22 | #include 23 | 24 | #include 25 | #if HAVE_ERR 26 | # include 27 | #endif 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #include "slant-collectd.h" 42 | #include "extern.h" 43 | #include "db.h" 44 | 45 | #ifndef _PATH_VAREMPTY 46 | # define _PATH_VAREMPTY "/var/empty" 47 | #endif 48 | 49 | static sig_atomic_t doexit = 0; 50 | 51 | static void 52 | sig(int sig) 53 | { 54 | 55 | doexit = 1; 56 | } 57 | 58 | static void 59 | update_interval(struct ort *db, time_t span, 60 | size_t have, size_t allowed, 61 | const struct record *first, const struct record *last, 62 | enum interval ival, time_t now, const struct record *r) 63 | { 64 | 65 | assert(allowed > 0); 66 | 67 | if (NULL != first && first->ctime + span > now) { 68 | /* Update the current entry. */ 69 | assert(NULL != first); 70 | assert(NULL != last); 71 | db_record_update_current(db, 72 | first->entries + 1, 73 | first->cpu + r->cpu, 74 | first->mem + r->mem, 75 | first->nettx + r->nettx, 76 | first->netrx + r->netrx, 77 | first->discread + r->discread, 78 | first->discwrite + r->discwrite, 79 | first->nprocs + r->nprocs, 80 | first->rprocs + r->rprocs, 81 | first->nfiles + r->nfiles, 82 | first->id); 83 | } else if (have > allowed) { 84 | /* New entry: shift end of circular queue. */ 85 | assert(NULL != first); 86 | assert(NULL != last); 87 | db_record_update_tail(db, now, 1, 88 | r->cpu, r->mem, r->nettx, r->netrx, 89 | r->discread, r->discwrite, r->nprocs, 90 | r->rprocs, r->nfiles, last->id); 91 | } else { 92 | /* New entry. */ 93 | db_record_insert(db, now, 1, 94 | r->cpu, r->mem, r->nettx, r->netrx, 95 | r->discread, r->discwrite, r->nprocs, 96 | r->rprocs, r->nfiles, ival); 97 | } 98 | } 99 | 100 | static void 101 | printinit(const struct sysinfo *p) 102 | { 103 | time_t t; 104 | 105 | t = sysinfo_get_boottime(p); 106 | printf("# Boottime: %s", ctime(&t)); 107 | printf("# Version: " VERSION "\n"); 108 | } 109 | 110 | static void 111 | print(const struct sysinfo *p) 112 | { 113 | 114 | printf("%9.1f%% %9.1f%% " 115 | "%10" PRId64 " %10" PRId64 " " 116 | "%10" PRId64 " %10" PRId64 " " 117 | "%9.1f%% %9.1f%% %9.1f%%\n", 118 | sysinfo_get_cpu_avg(p), 119 | sysinfo_get_mem_avg(p), 120 | sysinfo_get_nettx_avg(p), 121 | sysinfo_get_netrx_avg(p), 122 | sysinfo_get_discread_avg(p), 123 | sysinfo_get_discwrite_avg(p), 124 | sysinfo_get_nprocs(p), 125 | sysinfo_get_rprocs(p), 126 | sysinfo_get_nfiles(p)); 127 | } 128 | 129 | /* 130 | * Initialise the database by updating our runtime information in the 131 | * "system" table. 132 | * Return zero on failure, non-zero on success. 133 | */ 134 | static int 135 | init(struct ort *db, const struct sysinfo *p) 136 | { 137 | struct system *s; 138 | struct utsname uts; 139 | const char *mach, *ver, *rel, *sys; 140 | 141 | if (-1 == uname(&uts)) { 142 | warn(NULL); 143 | return 0; 144 | } 145 | 146 | mach = uts.machine; 147 | ver = uts.version; 148 | rel = uts.release; 149 | sys = uts.sysname; 150 | 151 | db_trans_open(db, 2, 0); 152 | 153 | if (NULL != (s = db_system_get_id(db, 1))) { 154 | db_system_update_all(db, 155 | sysinfo_get_boottime(p), /* boot */ 156 | &mach, /* machine */ 157 | &ver, /* osversion */ 158 | &rel, /* osrelease */ 159 | &sys, /* sysname */ 160 | 1 /* id */ ); 161 | db_system_free(s); 162 | } else 163 | db_system_insert(db, 164 | sysinfo_get_boottime(p), /* boot */ 165 | &mach, /* machine */ 166 | &ver, /* osversion */ 167 | &rel, /* osrelease */ 168 | &sys, /* sysname */ 169 | 1 /* id */ ); 170 | 171 | db_trans_commit(db, 2); 172 | return 1; 173 | } 174 | 175 | /* 176 | * Update the database "db" given the current record "p" and all 177 | * existing database records "rq". 178 | */ 179 | static void 180 | update(struct ort *db, const struct sysinfo *p, 181 | const struct record_q *rq) 182 | { 183 | size_t bymin = 0, byhour = 0, byqmin = 0, 184 | byday = 0, byweek = 0, byyear = 0; 185 | time_t t = time(NULL); 186 | struct record rr; 187 | const struct record *r, 188 | *first_bymin = NULL, *last_bymin = NULL, 189 | *first_byqmin = NULL, *last_byqmin = NULL, 190 | *first_byhour = NULL, *last_byhour = NULL, 191 | *first_byday = NULL, *last_byday = NULL, 192 | *first_byweek = NULL, *last_byweek = NULL, 193 | *first_byyear = NULL, *last_byyear = NULL; 194 | 195 | memset(&rr, 0, sizeof(struct record)); 196 | rr.cpu = sysinfo_get_cpu_avg(p); 197 | rr.mem = sysinfo_get_mem_avg(p); 198 | rr.nettx = sysinfo_get_nettx_avg(p); 199 | rr.netrx = sysinfo_get_netrx_avg(p); 200 | rr.discread = sysinfo_get_discread_avg(p); 201 | rr.discwrite = sysinfo_get_discwrite_avg(p); 202 | rr.nprocs = sysinfo_get_nprocs(p); 203 | rr.rprocs = sysinfo_get_rprocs(p); 204 | rr.nfiles = sysinfo_get_nfiles(p); 205 | 206 | /* 207 | * First count what we have. 208 | * We need this when determining how many "spare" entries to 209 | * keep in any given interval, e.g., we keep 5 minutes of 210 | * quarter-minute interval data, but only really need the last 211 | * single minute for accumulation. 212 | */ 213 | 214 | TAILQ_FOREACH(r, rq, _entries) 215 | switch (r->interval) { 216 | case INTERVAL_byqmin: 217 | if (NULL == first_byqmin) 218 | first_byqmin = r; 219 | last_byqmin = r; 220 | byqmin++; 221 | break; 222 | case INTERVAL_bymin: 223 | if (NULL == first_bymin) 224 | first_bymin = r; 225 | last_bymin = r; 226 | bymin++; 227 | break; 228 | case INTERVAL_byhour: 229 | if (NULL == first_byhour) 230 | first_byhour = r; 231 | last_byhour = r; 232 | byhour++; 233 | break; 234 | case INTERVAL_byday: 235 | if (NULL == first_byday) 236 | first_byday = r; 237 | last_byday = r; 238 | byday++; 239 | break; 240 | case INTERVAL_byweek: 241 | if (NULL == first_byweek) 242 | first_byweek = r; 243 | last_byweek = r; 244 | byweek++; 245 | break; 246 | case INTERVAL_byyear: 247 | if (NULL == first_byyear) 248 | first_byyear = r; 249 | last_byyear = r; 250 | byyear++; 251 | break; 252 | } 253 | 254 | db_trans_open(db, 1, 0); 255 | 256 | /* 40 (10 minute) backlog of quarter-minute entries. */ 257 | 258 | if (byqmin > (4 * 10)) { 259 | assert(NULL != last_byqmin); 260 | assert(NULL != first_byqmin); 261 | db_record_update_tail(db, t, 1, 262 | rr.cpu, rr.mem, rr.nettx, rr.netrx, 263 | rr.discread, rr.discwrite, rr.nprocs, 264 | rr.rprocs, rr.nfiles, last_byqmin->id); 265 | } else 266 | db_record_insert(db, t, 1, 267 | rr.cpu, rr.mem, rr.nettx, rr.netrx, 268 | rr.discread, rr.discwrite, rr.nprocs, 269 | rr.rprocs, rr.nfiles, INTERVAL_byqmin); 270 | 271 | /* 300 (5 hours) backlog of by-minute entries. */ 272 | 273 | update_interval(db, 60, bymin, 274 | 60 * 5, first_bymin, last_bymin, 275 | INTERVAL_bymin, t, &rr); 276 | 277 | /* 96 (5 days) backlog of by-hour entries. */ 278 | 279 | update_interval(db, 60 * 60, byhour, 280 | 24 * 5, first_byhour, last_byhour, 281 | INTERVAL_byhour, t, &rr); 282 | 283 | /* 28 (4 weeks) backlog of by-day entries. */ 284 | 285 | update_interval(db, 60 * 60 * 24, byday, 286 | 7 * 4, first_byday, last_byday, 287 | INTERVAL_byday, t, &rr); 288 | 289 | /* 104 (two year) backlog of by-week entries. */ 290 | 291 | update_interval(db, 60 * 60 * 24 * 7, byweek, 292 | 52 * 2, first_byday, last_byweek, 293 | INTERVAL_byweek, t, &rr); 294 | 295 | /* Endless backlog of yearly entries. */ 296 | 297 | update_interval(db, 60 * 60 * 24 * 365, byyear, 298 | SIZE_MAX, first_byyear, last_byyear, 299 | INTERVAL_byyear, t, &rr); 300 | 301 | db_trans_commit(db, 1); 302 | } 303 | 304 | static void 305 | cfg_free(struct syscfg *cfg) 306 | { 307 | size_t i; 308 | 309 | for (i = 0; i < cfg->discsz; i++) 310 | free(cfg->discs[i]); 311 | for (i = 0; i < cfg->cmdsz; i++) 312 | free(cfg->cmds[i]); 313 | 314 | free(cfg->discs); 315 | free(cfg->cmds); 316 | } 317 | 318 | int 319 | main(int argc, char *argv[]) 320 | { 321 | struct ort *db = NULL; 322 | struct record_q *rq; 323 | struct sysinfo *info; 324 | int c, rc = 0, noop = 0, verb = 0; 325 | const char *dbfile = "/var/www/data/slant.db"; 326 | char *d, *discs = NULL, *procs = NULL, *tofree; 327 | struct syscfg cfg; 328 | sigset_t sset; 329 | struct timespec timeo; 330 | 331 | timeo.tv_nsec = 0; 332 | timeo.tv_sec = 15; 333 | 334 | /* 335 | * FIXME: relax this restriction. 336 | * This is just because we can't use pledge() due to the 337 | * sysctls we call. 338 | * So we need to use chroot just for some basic security from 339 | * polluting our database. 340 | * Once we have unveil(), this will no longer be necessary. 341 | */ 342 | 343 | if (0 != getuid()) 344 | errx(EXIT_FAILURE, "must be run as root"); 345 | 346 | memset(&cfg, 0, sizeof(struct syscfg)); 347 | 348 | while (-1 != (c = getopt(argc, argv, "d:nvf:p:"))) 349 | switch (c) { 350 | case 'd': 351 | discs = optarg; 352 | break; 353 | case 'f': 354 | dbfile = optarg; 355 | break; 356 | case 'n': 357 | noop = 1; 358 | break; 359 | case 'p': 360 | procs = optarg; 361 | break; 362 | case 'v': 363 | verb = 1; 364 | break; 365 | default: 366 | goto usage; 367 | } 368 | 369 | argc -= optind; 370 | argv += optind; 371 | 372 | /* XXX: hack around ksql(3) exit when receives signal. */ 373 | 374 | if (SIG_ERR == signal(SIGINT, SIG_IGN)) 375 | err(EXIT_FAILURE, "signal"); 376 | if (SIG_ERR == signal(SIGTERM, SIG_IGN)) 377 | err(EXIT_FAILURE, "signal"); 378 | 379 | if (! noop && 380 | (db = db_open_logging(dbfile, NULL, warnx, NULL)) == NULL) 381 | errx(EXIT_FAILURE, "%s", dbfile); 382 | 383 | /* FIXME: once we have unveil, this is moot. */ 384 | 385 | #ifndef __linux__ 386 | if (-1 == chroot(_PATH_VAREMPTY)) 387 | err(EXIT_FAILURE, "%s", _PATH_VAREMPTY); 388 | else if (-1 == chdir("/")) 389 | err(EXIT_FAILURE, "/"); 390 | #endif 391 | 392 | if (NULL != db) 393 | db_role(db, ROLE_produce); 394 | 395 | /* 396 | * From here on our, use the "out" label for bailing on errors, 397 | * which will clean up behind us. 398 | * Let SIGINT and SIGTERM trigger us into exiting safely. 399 | */ 400 | 401 | if (NULL != discs) { 402 | if (NULL == (tofree = strdup(discs))) 403 | err(EXIT_FAILURE, NULL); 404 | discs = tofree; 405 | while (NULL != (d = strsep(&discs, ","))) { 406 | if ('\0' == d[0]) 407 | continue; 408 | cfg.discs = reallocarray 409 | (cfg.discs, 410 | cfg.discsz + 1, 411 | sizeof(char *)); 412 | if (NULL == cfg.discs) 413 | err(EXIT_FAILURE, NULL); 414 | cfg.discs[cfg.discsz] = strdup(d); 415 | if (NULL == cfg.discs[cfg.discsz]) 416 | err(EXIT_FAILURE, NULL); 417 | cfg.discsz++; 418 | } 419 | free(tofree); 420 | } 421 | 422 | if (NULL != procs) { 423 | if (NULL == (tofree = strdup(procs))) 424 | err(EXIT_FAILURE, NULL); 425 | procs = tofree; 426 | while (NULL != (d = strsep(&procs, ","))) { 427 | if ('\0' == d[0]) 428 | continue; 429 | cfg.cmds = reallocarray 430 | (cfg.cmds, 431 | cfg.cmdsz + 1, 432 | sizeof(char *)); 433 | if (NULL == cfg.cmds) 434 | err(EXIT_FAILURE, NULL); 435 | cfg.cmds[cfg.cmdsz] = strdup(d); 436 | if (NULL == cfg.cmds[cfg.cmdsz]) 437 | err(EXIT_FAILURE, NULL); 438 | cfg.cmdsz++; 439 | } 440 | free(tofree); 441 | } 442 | 443 | if (NULL == (info = sysinfo_alloc())) 444 | goto out; 445 | 446 | if (SIG_ERR == signal(SIGINT, sig) || 447 | SIG_ERR == signal(SIGTERM, sig)) { 448 | warn("signal"); 449 | goto out; 450 | } 451 | 452 | /* First, block SIGINT and SIGTERM. */ 453 | 454 | if (-1 == sigemptyset(&sset)) { 455 | warn("sigemptyset"); 456 | goto out; 457 | } else if (-1 == sigaddset(&sset, SIGINT)) { 458 | warn("sigaddset"); 459 | goto out; 460 | } else if (-1 == sigaddset(&sset, SIGTERM)) { 461 | warn("sigaddset"); 462 | goto out; 463 | } else if (-1 == sigprocmask(SIG_BLOCK, &sset, NULL)) { 464 | warn("sigprocmask"); 465 | goto out; 466 | } 467 | 468 | /* 469 | * Now invert the signal mask. 470 | * We'll use this in ppoll(2) so that receiving the signal will 471 | * cut short the system call and we can exit. 472 | */ 473 | 474 | if (-1 == sigfillset(&sset)) { 475 | warn("sigfillset"); 476 | goto out; 477 | } else if (-1 == sigdelset(&sset, SIGINT)) { 478 | warn("sigdelset"); 479 | goto out; 480 | } else if (-1 == sigdelset(&sset, SIGTERM)) { 481 | warn("sigdelset"); 482 | goto out; 483 | } 484 | 485 | if (NULL != db && ! init(db, info)) 486 | goto out; 487 | 488 | if (verb) 489 | printinit(info); 490 | 491 | /* 492 | * Now enter our main loop. 493 | * The body will run every 15 seconds. 494 | * Start each iteration by grabbing the current system state 495 | * using sysctl(3). 496 | * Then grab what we have in the database. 497 | * Lastly, modify the database state given our current. 498 | */ 499 | 500 | while ( ! doexit) { 501 | if ( ! sysinfo_update(&cfg, info)) 502 | goto out; 503 | if (NULL != db) { 504 | rq = db_record_list_lister(db); 505 | update(db, info, rq); 506 | db_record_freeq(rq); 507 | } 508 | if (verb) 509 | print(info); 510 | 511 | /* Wait 15 seconds or until we signal. */ 512 | 513 | if (-1 == ppoll(NULL, 0, &timeo, &sset) && 514 | EINTR != errno) { 515 | warn("ppoll"); 516 | goto out; 517 | } 518 | } 519 | 520 | rc = 1; 521 | out: 522 | cfg_free(&cfg); 523 | sysinfo_free(info); 524 | db_close(db); 525 | return rc ? EXIT_SUCCESS : EXIT_FAILURE; 526 | usage: 527 | fprintf(stderr, "usage: %s " 528 | "[-nv] " 529 | "[-d discs] " 530 | "[-f dbfile]\n", getprogname()); 531 | return EXIT_FAILURE; 532 | } 533 | -------------------------------------------------------------------------------- /slant-http.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2018 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #if HAVE_SYS_QUEUE 20 | # include 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include "extern.h" 41 | #include "slant.h" 42 | 43 | /* 44 | * Close out a connection (its file descriptor). 45 | * This is sensitive to whether we're https (tls_close) or not. 46 | * Return zero if the closing needs reentrancy (we need to re-call the 47 | * function), non-zero if the connection has closed. 48 | */ 49 | static int 50 | http_close_inner(WINDOW *errwin, struct node *n, time_t t) 51 | { 52 | int c; 53 | 54 | if ( ! n->addrs.https) { 55 | close(n->xfer.pfd->fd); 56 | n->xfer.pfd->fd = -1; 57 | return 1; 58 | } 59 | 60 | /* TODO: close timeout handler? */ 61 | 62 | c = tls_close(n->xfer.tls); 63 | if (TLS_WANT_POLLIN == c) { 64 | n->xfer.lastio = t; 65 | n->xfer.pfd->events = POLLIN; 66 | return 0; 67 | } else if (TLS_WANT_POLLOUT == c) { 68 | n->xfer.lastio = t; 69 | n->xfer.pfd->events = POLLOUT; 70 | return 0; 71 | } 72 | 73 | close(n->xfer.pfd->fd); 74 | n->xfer.pfd->fd = -1; 75 | return 1; 76 | } 77 | 78 | int 79 | http_close_err(struct out *out, struct node *n, time_t t) 80 | { 81 | int c; 82 | 83 | if (0 != (c = http_close_inner(out->errwin, n, t))) { 84 | n->addrs.curaddr = 85 | (n->addrs.curaddr + 1) % n->addrs.addrsz; 86 | n->state = STATE_CONNECT_WAITING; 87 | n->waitstart = t; 88 | return 1; 89 | } 90 | 91 | n->state = STATE_CLOSE_ERR; 92 | return 1; 93 | } 94 | 95 | /* 96 | * After closing out a connection, we're ready to parse the HTTP buffer 97 | * placed into n->xfer. 98 | * Returns zero on failure (fatal), non-zero on success (or non-fatal 99 | * errors in the data). 100 | */ 101 | static int 102 | http_close_done_ok(struct out *out, struct node *n, time_t t) 103 | { 104 | char *end, *sv, *start = n->xfer.rbuf; 105 | size_t len, sz = n->xfer.rbufsz; 106 | int rc, httpok = 0; 107 | 108 | n->state = STATE_CONNECT_WAITING; 109 | n->waitstart = t; 110 | 111 | /* 112 | * Start with HTTP headers, reading up until the double CRLF. 113 | * We care about the HTTP version only. 114 | */ 115 | 116 | while (NULL != (end = memmem(start, sz, "\r\n", 2))) { 117 | /* 118 | * NUL-terminate the current line. 119 | * Position the next line ("start") after the CRLF. 120 | * Keep the current start of the line in "sv". 121 | * Adjust buffer size less current line and CRLF. 122 | */ 123 | 124 | *end = '\0'; 125 | sv = start; 126 | len = end - start; 127 | start = end + 2; 128 | sz -= len + 2; 129 | 130 | /* Not allowed, but harmless. */ 131 | 132 | if (0 == len) 133 | break; 134 | 135 | /* 136 | * Do we have the status line? 137 | * If so, all we want to see is whether we have an 138 | * HTTP/200 or not. 139 | * XXX: if we were to have a redirect, that should be 140 | * handled here by re-initialising the URL and then 141 | * re-acquiring the IPs. 142 | * However, slant currently can't handle that because it 143 | * needs to have a subprocess for DNS (async DNS is too 144 | * damn complicated, and we'd rather not have a dns 145 | * pledge), which it doesn't have yet. 146 | */ 147 | 148 | if (len >= 13 && 149 | (0 == memcmp(sv, "HTTP/1.0 200 ", 13) || 150 | 0 == memcmp(sv, "HTTP/1.1 200 ", 13))) { 151 | httpok = 1; 152 | continue; 153 | } 154 | 155 | /* Additional queries...? */ 156 | } 157 | 158 | /* 159 | * If we encountered an HTTP/200, then we're good to investigate 160 | * the message itself. 161 | * Otherwise, dump the entire HTTP response to our log file. 162 | */ 163 | 164 | if ( ! httpok) { 165 | xwarnx(out, "bad HTTP response (%lld seconds): %s", 166 | (long long)t - n->xfer.start, n->host); 167 | fprintf(out->errs, "------>------\n"); 168 | fprintf(out->errs, "%.*s\n", (int)n->xfer.rbufsz, 169 | n->xfer.rbuf); 170 | fprintf(out->errs, "------<------\n"); 171 | fflush(out->errs); 172 | rc = 1; 173 | } else if ((rc = json_parse(out, n, start, sz)) > 0) { 174 | /* 175 | * XXX: should a bad (rc == 0) JSON parse really trigger 176 | * the fatal error condition? 177 | * First let's see if we pick this up in reality. 178 | */ 179 | n->dirty = 1; 180 | n->lastseen = t; 181 | 182 | /* 183 | * Compute drift given our transfer times and the 184 | * timestamp given in the JSON query. 185 | */ 186 | 187 | if (n->recs->has_timestamp) { 188 | assert(t >= n->xfer.start); 189 | n->drift = n->recs->timestamp - 190 | (t + (t - n->xfer.start) / 2); 191 | } else 192 | n->drift = 0; 193 | } 194 | 195 | free(n->xfer.rbuf); 196 | n->xfer.rbuf = NULL; 197 | n->xfer.rbufsz = 0; 198 | return rc >= 0; 199 | } 200 | 201 | int 202 | http_close_done(struct out *out, struct node *n, time_t t) 203 | { 204 | int c; 205 | 206 | if ((c = http_close_inner(out->errwin, n, t)) > 0) 207 | return http_close_done_ok(out, n, t); 208 | 209 | n->state = STATE_CLOSE_DONE; 210 | return 1; 211 | } 212 | 213 | /* 214 | * Prepare the write buffer. 215 | * Return zero on failure, non-zero on success. 216 | */ 217 | static int 218 | http_write_ready(struct out *out, struct node *n, time_t t) 219 | { 220 | int c; 221 | 222 | if (n->addrs.https) { 223 | c = tls_connect_socket(n->xfer.tls, 224 | n->xfer.pfd->fd, n->host); 225 | if (c < 0) { 226 | xwarnx(out, "tls_connect_socket: %s: %s", 227 | n->host, tls_error(n->xfer.tls)); 228 | } 229 | } 230 | 231 | n->xfer.wbufsz = n->xfer.wbufpos = 0; 232 | free(n->xfer.wbuf); 233 | n->xfer.wbuf = NULL; 234 | 235 | c = NULL != n->httpauth ? 236 | asprintf(&n->xfer.wbuf, 237 | "GET %s HTTP/1.0\r\n" 238 | "Host: %s\r\n" 239 | "Authorization: Basic %s\r\n" 240 | "\r\n", 241 | n->path, n->host, n->httpauth) : 242 | asprintf(&n->xfer.wbuf, 243 | "GET %s HTTP/1.0\r\n" 244 | "Host: %s\r\n" 245 | "\r\n", 246 | n->path, n->host); 247 | 248 | if (c < 0) { 249 | xwarn(out, NULL); 250 | return 0; 251 | } 252 | 253 | n->xfer.wbufsz = c; 254 | n->xfer.lastio = t; 255 | n->state = STATE_WRITE; 256 | 257 | if (n->addrs.https) 258 | n->xfer.pfd->events = POLLOUT|POLLIN; 259 | else 260 | n->xfer.pfd->events = POLLOUT; 261 | return 1; 262 | } 263 | 264 | /* 265 | * Initialise a socket descriptor to the current endpoint. 266 | * Moves into STATE_CONNECT for async connection, STATE_WRITE on 267 | * success, or calls http_close_err() on connection failure. 268 | * Returns zero on system failure, non-zero on success. 269 | */ 270 | int 271 | http_init_connect(struct out *out, struct node *n, time_t t) 272 | { 273 | int family, c, flags; 274 | socklen_t sslen; 275 | struct tls_config *cfg; 276 | 277 | memset(&n->xfer.ss, 0, sizeof(struct sockaddr_storage)); 278 | 279 | if (NULL == n->xfer.tls) { 280 | if (NULL == (n->xfer.tls = tls_client())) { 281 | xwarn(out, "tls_client"); 282 | return 0; 283 | } 284 | } else 285 | tls_reset(n->xfer.tls); 286 | 287 | if (NULL != n->xfer.tls) { 288 | if (NULL == (cfg = tls_config_new())) { 289 | xwarn(out, "tls_config_new"); 290 | return 0; 291 | } 292 | tls_config_set_protocols(cfg, TLS_PROTOCOLS_ALL); 293 | if (-1 == tls_configure(n->xfer.tls, cfg)) { 294 | xwarnx(out, "tls_configure: %s: %s", n->host, 295 | tls_error(n->xfer.tls)); 296 | return 0; 297 | } 298 | tls_config_free(cfg); 299 | } 300 | 301 | if (4 == n->addrs.addrs[n->addrs.curaddr].family) { 302 | family = PF_INET; 303 | ((struct sockaddr_in *)&n->xfer.ss)->sin_family = AF_INET; 304 | ((struct sockaddr_in *)&n->xfer.ss)->sin_port = 305 | htons(n->addrs.port); 306 | c = inet_pton(AF_INET, 307 | n->addrs.addrs[n->addrs.curaddr].ip, 308 | &((struct sockaddr_in *)&n->xfer.ss)->sin_addr); 309 | sslen = sizeof(struct sockaddr_in); 310 | } else { 311 | family = PF_INET6; 312 | ((struct sockaddr_in6 *)&n->xfer.ss)->sin6_family = AF_INET6; 313 | ((struct sockaddr_in6 *)&n->xfer.ss)->sin6_port = 314 | htons(n->addrs.port); 315 | c = inet_pton(AF_INET6, 316 | n->addrs.addrs[n->addrs.curaddr].ip, 317 | &((struct sockaddr_in6 *)&n->xfer.ss)->sin6_addr); 318 | sslen = sizeof(struct sockaddr_in6); 319 | } 320 | 321 | if (c < 0) { 322 | xwarn(out, "cannot convert: %s: %s", n->host, 323 | n->addrs.addrs[n->addrs.curaddr].ip); 324 | return 0; 325 | } else if (0 == c) { 326 | xwarnx(out, "cannot convert: %s: %s", n->host, 327 | n->addrs.addrs[n->addrs.curaddr].ip); 328 | return 0; 329 | } 330 | 331 | n->xfer.pfd->events = POLLOUT; 332 | n->xfer.pfd->fd = socket(family, SOCK_STREAM, 0); 333 | 334 | if (-1 == n->xfer.pfd->fd) { 335 | xwarn(out, "socket"); 336 | return 0; 337 | } 338 | 339 | /* Set up non-blocking mode. */ 340 | 341 | if (-1 == (flags = fcntl(n->xfer.pfd->fd, F_GETFL, 0))) { 342 | xwarn(out, "fcntl"); 343 | return 0; 344 | } 345 | if (-1 == fcntl(n->xfer.pfd->fd, F_SETFL, flags|O_NONBLOCK)) { 346 | xwarn(out, "fcntl"); 347 | return 0; 348 | } 349 | 350 | n->state = STATE_CONNECT; 351 | n->xfer.start = n->xfer.lastio = t; 352 | 353 | /* This is from connect(2): asynchronous connection. */ 354 | 355 | c = connect(n->xfer.pfd->fd, 356 | (struct sockaddr *)&n->xfer.ss, sslen); 357 | if (0 == c) 358 | return http_write_ready(out, n, t); 359 | 360 | /* Asynchronous connect... */ 361 | 362 | if (EINTR == errno || 363 | EINPROGRESS == errno) 364 | return 1; 365 | 366 | if (ETIMEDOUT == errno || 367 | ECONNREFUSED == errno || 368 | EHOSTUNREACH == errno || 369 | ENETDOWN == errno || 370 | ENETUNREACH == errno) { 371 | xwarn(out, "connect (transient): %s: %s", 372 | n->host, 373 | n->addrs.addrs[n->addrs.curaddr].ip); 374 | return http_close_err(out, n, t); 375 | } 376 | 377 | xwarn(out, "connect: %s: %s", n->host, 378 | n->addrs.addrs[n->addrs.curaddr].ip); 379 | 380 | return 0; 381 | } 382 | 383 | /* 384 | * Check for asynchronous connect(2). 385 | * Returns zero on system failure, non-zero on success. 386 | * Calls http_close_err() if connection fails or transitions to 387 | * STATE_WRITE on success. 388 | */ 389 | int 390 | http_connect(struct out *out, struct node *n, time_t t) 391 | { 392 | int c; 393 | int error = 0; 394 | socklen_t len = sizeof(error); 395 | 396 | assert(-1 != n->xfer.pfd->fd); 397 | 398 | if ((POLLNVAL & n->xfer.pfd->revents) || 399 | (POLLERR & n->xfer.pfd->revents)) { 400 | xwarn(out, "poll (connect): %lld seconds, %s: %s", 401 | (long long)t - n->xfer.start, n->host, 402 | n->addrs.addrs[n->addrs.curaddr].ip); 403 | return 0; 404 | } else if (POLLHUP & n->xfer.pfd->revents) { 405 | xwarnx(out, "poll hup (connect): %lld seconds, %s: %s", 406 | (long long)t - n->xfer.start, n->host, 407 | n->addrs.addrs[n->addrs.curaddr].ip); 408 | return http_close_err(out, n, t); 409 | } 410 | 411 | assert(t >= n->xfer.lastio); 412 | if (t - n->xfer.lastio > n->timeout) { 413 | xwarnx(out, "connect timeout: %lld seconds, %s: %s", 414 | (long long)t - n->xfer.start, n->host, 415 | n->addrs.addrs[n->addrs.curaddr].ip); 416 | return http_close_err(out, n, t); 417 | } 418 | 419 | if ( ! (POLLOUT & n->xfer.pfd->revents)) 420 | return 1; 421 | 422 | c = getsockopt(n->xfer.pfd->fd, 423 | SOL_SOCKET, SO_ERROR, &error, &len); 424 | 425 | if (c < 0) { 426 | xwarn(out, "getsockopt: %s: %s", 427 | n->host, n->addrs.addrs[n->addrs.curaddr].ip); 428 | return 0; 429 | } else if (0 == error) 430 | return http_write_ready(out, n, t); 431 | 432 | errno = error; 433 | if (ETIMEDOUT == errno || 434 | ECONNREFUSED == errno || 435 | EHOSTUNREACH == errno || 436 | ENETDOWN == errno || 437 | ENETUNREACH == errno) { 438 | xwarn(out, "getsockopt (transient): %s: %s", 439 | n->host, n->addrs.addrs[n->addrs.curaddr].ip); 440 | return http_close_err(out, n, t); 441 | } 442 | 443 | xwarn(out, "getsockopt: %s: %s", 444 | n->host, n->addrs.addrs[n->addrs.curaddr].ip); 445 | return 0; 446 | } 447 | 448 | /* 449 | * Write to the file descriptor. 450 | * Returns zero on system failure, non-zero on success. 451 | * When the request has been written, update state to be 452 | * STATE_READ. 453 | * On connect/write requests, calls http_close_err() for further 454 | * changing of state. 455 | */ 456 | int 457 | http_write(struct out *out, struct node *n, time_t t) 458 | { 459 | ssize_t ssz; 460 | 461 | assert(-1 != n->xfer.pfd->fd); 462 | assert(NULL != n->xfer.wbuf); 463 | assert(n->xfer.wbufsz > 0); 464 | 465 | if ((POLLNVAL & n->xfer.pfd->revents) || 466 | (POLLERR & n->xfer.pfd->revents)) { 467 | xwarn(out, "poll errors: %s: %s", n->host, 468 | n->addrs.addrs[n->addrs.curaddr].ip); 469 | return 0; 470 | } else if (POLLHUP & n->xfer.pfd->revents) { 471 | xwarnx(out, "poll hup (write): %lld seconds, %s: %s", 472 | (long long)t - n->xfer.start, n->host, 473 | n->addrs.addrs[n->addrs.curaddr].ip); 474 | return http_close_err(out, n, t); 475 | } 476 | 477 | assert(t >= n->xfer.lastio); 478 | if (t - n->xfer.lastio > n->timeout) { 479 | xwarnx(out, "write timeout: %lld seconds, %s: %s", 480 | (long long)t - n->xfer.start, n->host, 481 | n->addrs.addrs[n->addrs.curaddr].ip); 482 | return http_close_err(out, n, t); 483 | } 484 | 485 | if ( ! (POLLOUT & n->xfer.pfd->revents) && 486 | ! (POLLIN & n->xfer.pfd->revents)) 487 | return 1; 488 | 489 | if (n->addrs.https) { 490 | ssz = tls_write(n->xfer.tls, 491 | n->xfer.wbuf + n->xfer.wbufpos, 492 | n->xfer.wbufsz); 493 | if (TLS_WANT_POLLOUT == ssz) { 494 | n->xfer.pfd->events = POLLOUT; 495 | return 1; 496 | } else if (TLS_WANT_POLLIN == ssz) { 497 | n->xfer.pfd->events = POLLIN; 498 | return 1; 499 | } else if (ssz < 0) { 500 | xwarnx(out, "tls_write: %s: %s: %s", 501 | n->host, 502 | n->addrs.addrs[n->addrs.curaddr].ip, 503 | tls_error(n->xfer.tls)); 504 | return http_close_err(out, n, t); 505 | } 506 | } else { 507 | ssz = write(n->xfer.pfd->fd, n->xfer.wbuf + 508 | n->xfer.wbufpos, n->xfer.wbufsz); 509 | if (ssz < 0) { 510 | xwarn(out, "write: %s: %s", n->host, 511 | n->addrs.addrs[n->addrs.curaddr].ip); 512 | return http_close_err(out, n, t); 513 | } 514 | } 515 | 516 | n->xfer.lastio = t; 517 | n->xfer.wbufsz -= ssz; 518 | n->xfer.wbufpos += ssz; 519 | 520 | if (n->xfer.wbufsz > 0) 521 | return 1; 522 | 523 | free(n->xfer.wbuf); 524 | n->xfer.wbuf = NULL; 525 | n->state = STATE_READ; 526 | free(n->xfer.rbuf); 527 | n->xfer.rbuf = NULL; 528 | n->xfer.rbufsz = 0; 529 | if (n->addrs.https) 530 | n->xfer.pfd->events = POLLOUT|POLLIN; 531 | else 532 | n->xfer.pfd->events = POLLIN; 533 | return 1; 534 | } 535 | 536 | /* 537 | * Read from the file descriptor. 538 | * Returns zero on system failure, non-zero on success. 539 | * When the file descriptor has been read, sets state to 540 | * STATE_CONNECT_WAITING. 541 | */ 542 | int 543 | http_read(struct out *out, struct node *n, time_t t) 544 | { 545 | ssize_t ssz; 546 | char buf[1024 * 5]; 547 | void *pp; 548 | 549 | assert(STATE_READ == n->state); 550 | assert(-1 != n->xfer.pfd->fd); 551 | 552 | /* Check for poll(2) errors and readability. */ 553 | 554 | if ((POLLNVAL & n->xfer.pfd->revents) || 555 | (POLLERR & n->xfer.pfd->revents)) { 556 | xwarnx(out, "poll errors: %s: %s", n->host, 557 | n->addrs.addrs[n->addrs.curaddr].ip); 558 | return 0; 559 | } else if (POLLHUP & n->xfer.pfd->revents) { 560 | xwarnx(out, "poll hup (read): %lld seconds, %s: %s", 561 | (long long)t - n->xfer.start, n->host, 562 | n->addrs.addrs[n->addrs.curaddr].ip); 563 | return http_close_err(out, n, t); 564 | } 565 | 566 | assert(t >= n->xfer.lastio); 567 | if (t - n->xfer.lastio > n->timeout) { 568 | xwarnx(out, "read timeout: %lld seconds, %s: %s", 569 | (long long)t - n->xfer.start, n->host, 570 | n->addrs.addrs[n->addrs.curaddr].ip); 571 | return http_close_err(out, n, t); 572 | } 573 | 574 | if ( ! (POLLOUT & n->xfer.pfd->revents) && 575 | ! (POLLIN & n->xfer.pfd->revents)) 576 | return 1; 577 | 578 | /* Read into a static buffer. */ 579 | 580 | if (n->addrs.https) { 581 | ssz = tls_read(n->xfer.tls, buf, sizeof(buf)); 582 | if (TLS_WANT_POLLOUT == ssz) { 583 | n->xfer.pfd->events = POLLOUT; 584 | return 1; 585 | } else if (TLS_WANT_POLLIN == ssz) { 586 | n->xfer.pfd->events = POLLIN; 587 | return 1; 588 | } else if (ssz < 0) { 589 | xwarnx(out, "tls_read: %s: %s: %s", 590 | n->host, 591 | n->addrs.addrs[n->addrs.curaddr].ip, 592 | tls_error(n->xfer.tls)); 593 | return http_close_err(out, n, t); 594 | } 595 | } else { 596 | ssz = read(n->xfer.pfd->fd, buf, sizeof(buf)); 597 | if (ssz < 0) { 598 | xwarn(out, "read: %s: %s", n->host, 599 | n->addrs.addrs[n->addrs.curaddr].ip); 600 | return http_close_err(out, n, t); 601 | } 602 | } 603 | 604 | n->xfer.lastio = t; 605 | 606 | if (0 == ssz) 607 | return http_close_done(out, n, t); 608 | 609 | /* Copy static into dynamic buffer. */ 610 | 611 | pp = realloc(n->xfer.rbuf, n->xfer.rbufsz + ssz); 612 | if (NULL == pp) { 613 | xwarn(out, NULL); 614 | return 0; 615 | } 616 | n->xfer.rbuf = pp; 617 | memcpy(n->xfer.rbuf + n->xfer.rbufsz, buf, ssz); 618 | n->xfer.rbufsz += ssz; 619 | return 1; 620 | } 621 | 622 | -------------------------------------------------------------------------------- /slant.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2018 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #if HAVE_SYS_QUEUE 20 | # include 21 | #endif 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #if HAVE_ERR 29 | # include 30 | #endif 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include 44 | #include 45 | 46 | #include "extern.h" 47 | #include "slant.h" 48 | #include "json.h" 49 | 50 | static volatile sig_atomic_t sigged; 51 | 52 | static void 53 | dosig(int code) 54 | { 55 | 56 | sigged = 1; 57 | } 58 | 59 | void 60 | recset_free(struct recset *r) 61 | { 62 | 63 | if (NULL == r) 64 | return; 65 | 66 | free(r->version); 67 | jsmn_system_clear(&r->system); 68 | jsmn_record_free_array(r->byqmin, r->byqminsz); 69 | jsmn_record_free_array(r->bymin, r->byminsz); 70 | jsmn_record_free_array(r->byhour, r->byhoursz); 71 | jsmn_record_free_array(r->byday, r->bydaysz); 72 | jsmn_record_free_array(r->byweek, r->byweeksz); 73 | jsmn_record_free_array(r->byyear, r->byyearsz); 74 | } 75 | 76 | static void 77 | nodes_free(struct node *n, size_t sz) 78 | { 79 | size_t i; 80 | 81 | for (i = 0; i < sz; i++) { 82 | if (NULL != n[i].xfer.tls) 83 | tls_close(n[i].xfer.tls); 84 | if (-1 != n[i].xfer.pfd->fd) 85 | close(n[i].xfer.pfd->fd); 86 | tls_free(n[i].xfer.tls); 87 | free(n[i].host); 88 | free(n[i].xfer.wbuf); 89 | free(n[i].xfer.rbuf); 90 | recset_free(n[i].recs); 91 | free(n[i].recs); 92 | free(n[i].httpauth); 93 | } 94 | 95 | free(n); 96 | } 97 | 98 | /* 99 | * Run a single step of the state machine. 100 | * For each node, we examine the current state (n->state) and perform 101 | * some task based upon that. 102 | * Return <0 on some sort of error condition or the number of nodes that 103 | * have changed, which means some sort of window update event should 104 | * occur to reflect the change. 105 | */ 106 | static int 107 | nodes_update(struct out *out, struct node *n, size_t sz, time_t t) 108 | { 109 | size_t i; 110 | int dirty = 0; 111 | 112 | for (i = 0; i < sz; i++) { 113 | switch (n[i].state) { 114 | case STATE_CONNECT_WAITING: 115 | if (n[i].waitstart + n[i].waittime >= t) 116 | break; 117 | n[i].state = STATE_CONNECT_READY; 118 | n[i].dirty = 1; 119 | break; 120 | case STATE_CONNECT_READY: 121 | if ( ! http_init_connect(out, &n[i], t)) 122 | return -1; 123 | break; 124 | case STATE_CONNECT: 125 | if ( ! http_connect(out, &n[i], t)) 126 | return -1; 127 | break; 128 | case STATE_WRITE: 129 | if ( ! http_write(out, &n[i], t)) 130 | return -1; 131 | break; 132 | case STATE_CLOSE_ERR: 133 | if ( ! http_close_err(out, &n[i], t)) 134 | return -1; 135 | break; 136 | case STATE_CLOSE_DONE: 137 | if ( ! http_close_done(out, &n[i], t)) 138 | return -1; 139 | break; 140 | case STATE_READ: 141 | if ( ! http_read(out, &n[i], t)) 142 | return -1; 143 | break; 144 | default: 145 | abort(); 146 | } 147 | 148 | if (n[i].dirty) 149 | dirty++; 150 | } 151 | 152 | return dirty; 153 | } 154 | 155 | /* 156 | * Sort comparator for memory usage. 157 | * Needs to be run once per iteration. 158 | */ 159 | static int 160 | cmp_mem(const void *p1, const void *p2) 161 | { 162 | const struct node *n1 = p1, *n2 = p2; 163 | 164 | if (NULL == n1->recs || 165 | 0 == n1->recs->byqminsz) 166 | return 1; 167 | if (NULL == n2->recs || 168 | 0 == n2->recs->byqminsz) 169 | return -1; 170 | if (n1->recs->byqmin[0].mem < 171 | n2->recs->byqmin[0].mem) 172 | return 1; 173 | if (n1->recs->byqmin[0].mem > 174 | n2->recs->byqmin[0].mem) 175 | return -1; 176 | return 0; 177 | } 178 | 179 | /* 180 | * Sort comparator for CPU time. 181 | * Needs to be run once per iteration. 182 | */ 183 | static int 184 | cmp_cpu(const void *p1, const void *p2) 185 | { 186 | const struct node *n1 = p1, *n2 = p2; 187 | 188 | if (NULL == n1->recs || 189 | 0 == n1->recs->byqminsz) 190 | return 1; 191 | if (NULL == n2->recs || 192 | 0 == n2->recs->byqminsz) 193 | return -1; 194 | if (n1->recs->byqmin[0].cpu < 195 | n2->recs->byqmin[0].cpu) 196 | return 1; 197 | if (n1->recs->byqmin[0].cpu > 198 | n2->recs->byqmin[0].cpu) 199 | return -1; 200 | return 0; 201 | } 202 | 203 | /* 204 | * Sort comparator for hostnames. 205 | * Needs to only be run once for the list. 206 | */ 207 | static int 208 | cmp_host(const void *p1, const void *p2) 209 | { 210 | const struct node *n1 = p1, *n2 = p2; 211 | 212 | return strcmp(n1->host, n2->host); 213 | } 214 | 215 | /* 216 | * Common leading material for all logging messages. 217 | */ 218 | static void 219 | xloghead(struct out *out) 220 | { 221 | char buf[32]; 222 | struct tm *tm; 223 | time_t t = time(NULL); 224 | 225 | assert(NULL != out->errwin); 226 | tm = localtime(&t); 227 | strftime(buf, sizeof(buf), "%F %T", tm); 228 | waddstr(out->errwin, buf); 229 | fprintf(out->errs, "%s: ", buf); 230 | wprintw(out->errwin, " %lc ", L'\x2502'); 231 | } 232 | 233 | /* 234 | * Emit warning message with errno. 235 | */ 236 | void 237 | xwarn(struct out *out, const char *fmt, ...) 238 | { 239 | va_list ap; 240 | int er = errno; 241 | 242 | if (NULL != out->errwin) { 243 | xloghead(out); 244 | if (NULL != fmt) { 245 | va_start(ap, fmt); 246 | vwprintw(out->errwin, fmt, ap); 247 | va_end(ap); 248 | } 249 | wprintw(out->errwin, "%s%s (%d)\n", 250 | NULL == fmt ? "" : ": ", strerror(er), er); 251 | wrefresh(out->errwin); 252 | } 253 | 254 | if (NULL != fmt) { 255 | va_start(ap, fmt); 256 | vfprintf(out->errs, fmt, ap); 257 | va_end(ap); 258 | } 259 | fprintf(out->errs, "%s%s (%d)\n", 260 | NULL == fmt ? "" : ": ", strerror(er), er); 261 | fflush(out->errs); 262 | } 263 | 264 | /* 265 | * Emit warning message. 266 | */ 267 | void 268 | xwarnx(struct out *out, const char *fmt, ...) 269 | { 270 | va_list ap; 271 | 272 | if (NULL != out->errwin) { 273 | xloghead(out); 274 | wattron(out->errwin, A_BOLD); 275 | waddstr(out->errwin, "Warning"); 276 | wattroff(out->errwin, A_BOLD); 277 | waddstr(out->errwin, ": "); 278 | if (NULL != fmt) { 279 | va_start(ap, fmt); 280 | vwprintw(out->errwin, fmt, ap); 281 | va_end(ap); 282 | } 283 | waddch(out->errwin, '\n'); 284 | wrefresh(out->errwin); 285 | } 286 | 287 | fprintf(out->errs, "Warning: "); 288 | va_start(ap, fmt); 289 | vfprintf(out->errs, fmt, ap); 290 | va_end(ap); 291 | fputc('\n', out->errs); 292 | fflush(out->errs); 293 | } 294 | 295 | /* 296 | * Emit debugging message if "out->debug" has been set. 297 | */ 298 | void 299 | xdbg(struct out *out, const char *fmt, ...) 300 | { 301 | va_list ap; 302 | 303 | if ( ! out->debug) 304 | return; 305 | 306 | if (NULL != out->errwin) { 307 | xloghead(out); 308 | if (NULL != fmt) { 309 | va_start(ap, fmt); 310 | vwprintw(out->errwin, fmt, ap); 311 | va_end(ap); 312 | } 313 | waddch(out->errwin, '\n'); 314 | wrefresh(out->errwin); 315 | } 316 | 317 | if (NULL != fmt) { 318 | va_start(ap, fmt); 319 | vfprintf(out->errs, fmt, ap); 320 | va_end(ap); 321 | } 322 | fputc('\n', out->errs); 323 | fflush(out->errs); 324 | } 325 | 326 | /* 327 | * Construct a default layout depending on "maxx", the maximum number of 328 | * available columns, "maxy" for rows, and "n" nodes of length "nsz". 329 | * Put the results in "d". 330 | * This copies from the configuration, if applicable, or sets defaults. 331 | * Return zero on failure (fatal), non-zero on success. 332 | */ 333 | static int 334 | layout(struct config *cfg, struct out *out, size_t maxx, 335 | size_t maxy, const struct node *n, size_t nsz, struct draw *d) 336 | { 337 | size_t i; 338 | 339 | if (NULL != cfg->draw) { 340 | d->header = cfg->draw->header; 341 | d->errlog = cfg->draw->errlog; 342 | if (d->errlog >= maxy - d->header) 343 | return 0; 344 | } else { 345 | d->header = 1; 346 | if (maxy > 60) 347 | d->errlog = 10; 348 | else if (maxy > 40) 349 | d->errlog = 5; 350 | } 351 | 352 | #define ORD_CPU 0 /* Default order... */ 353 | #define ORD_MEM 1 354 | #define ORD_PROCS 2 355 | #define ORD_NET 3 356 | #define ORD_DISC 4 357 | #define ORD_LINK 5 358 | #define ORD_RPROCS 6 359 | #define ORD_HOST 7 360 | 361 | if (NULL != cfg->draw && cfg->draw->boxsz) { 362 | d->boxsz = cfg->draw->boxsz; 363 | d->maxline = cfg->draw->maxline; 364 | d->box = calloc(d->boxsz, sizeof(struct drawbox)); 365 | if (NULL == d->box) 366 | return -1; 367 | for (i = 0; i < d->boxsz; i++) 368 | d->box[i] = cfg->draw->box[i]; 369 | } else { 370 | d->boxsz = 8; 371 | d->maxline = 1; 372 | d->box = calloc(d->boxsz, sizeof(struct drawbox)); 373 | if (NULL == d->box) 374 | return -1; 375 | 376 | d->box[ORD_CPU].cat = DRAWCAT_CPU; 377 | d->box[ORD_MEM].cat = DRAWCAT_MEM; 378 | d->box[ORD_NET].cat = DRAWCAT_NET; 379 | d->box[ORD_DISC].cat = DRAWCAT_DISC; 380 | d->box[ORD_PROCS].cat = DRAWCAT_PROCS; 381 | d->box[ORD_LINK].cat = DRAWCAT_LINK; 382 | d->box[ORD_RPROCS].cat = DRAWCAT_RPROCS; 383 | d->box[ORD_HOST].cat = DRAWCAT_HOST; 384 | 385 | d->box[ORD_CPU].lines[0].line = 386 | LINE_QMIN_BARS | LINE_QMIN | LINE_HOUR; 387 | d->box[ORD_MEM].lines[0].line = 388 | LINE_QMIN_BARS | LINE_QMIN | LINE_HOUR; 389 | d->box[ORD_NET].lines[0].line = 390 | LINE_QMIN | LINE_HOUR; 391 | d->box[ORD_DISC].lines[0].line = 392 | LINE_QMIN | LINE_HOUR; 393 | d->box[ORD_PROCS].lines[0].line = 394 | LINE_QMIN_BARS | LINE_QMIN | LINE_HOUR; 395 | d->box[ORD_LINK].lines[0].line = 396 | LINK_IP | LINK_STATE | LINK_ACCESS; 397 | d->box[ORD_RPROCS].lines[0].line = LINE_QMIN; 398 | d->box[ORD_HOST].lines[0].line = HOST_RECORD; 399 | 400 | if (maxx > compute_width(n, nsz, d)) 401 | return 1; 402 | 403 | d->box[ORD_CPU].lines[0].line &= ~LINE_QMIN_BARS; 404 | d->box[ORD_MEM].lines[0].line &= ~LINE_QMIN_BARS; 405 | d->box[ORD_PROCS].lines[0].line &= ~LINE_QMIN_BARS; 406 | 407 | if (maxx > compute_width(n, nsz, d)) 408 | return 1; 409 | 410 | d->box[ORD_CPU].lines[0].line &= ~LINE_HOUR; 411 | d->box[ORD_MEM].lines[0].line &= ~LINE_HOUR; 412 | d->box[ORD_NET].lines[0].line &= ~LINE_HOUR; 413 | d->box[ORD_DISC].lines[0].line &= ~LINE_HOUR; 414 | d->box[ORD_PROCS].lines[0].line &= ~LINE_HOUR; 415 | 416 | if (maxx > compute_width(n, nsz, d)) 417 | return 1; 418 | 419 | d->box[ORD_LINK].lines[0].line &= ~(LINK_IP | LINK_STATE); 420 | } 421 | 422 | return maxx > compute_width(n, nsz, d); 423 | } 424 | 425 | int 426 | main(int argc, char *argv[]) 427 | { 428 | int c, first = 1, maxy, maxx; 429 | size_t i, sz; 430 | const char *cfgfile = NULL; 431 | struct node *n = NULL; 432 | struct pollfd *pfds = NULL; 433 | struct timespec ts; 434 | sigset_t mask, oldmask; 435 | time_t last, now; 436 | char *cp; 437 | struct draw d; 438 | struct config cfg; 439 | struct out out; 440 | 441 | if (NULL == getenv("HOME")) 442 | errx(EXIT_FAILURE, "no HOME directory defined"); 443 | if (NULL == setlocale(LC_ALL, "")) 444 | err(EXIT_FAILURE, NULL); 445 | 446 | /* Initial pledge. */ 447 | 448 | #if HAVE_PLEDGE 449 | if (-1 == pledge("cpath wpath tty rpath dns inet stdio", NULL)) 450 | err(EXIT_FAILURE, NULL); 451 | #endif 452 | 453 | memset(&out, 0, sizeof(struct out)); 454 | memset(&d, 0, sizeof(struct draw)); 455 | memset(&cfg, 0, sizeof(struct config)); 456 | 457 | out.debug = 1; 458 | 459 | /* 460 | * Open $HOME/.slant-errlog to catch any errors. 461 | * This way our error buffer in the window is saved. 462 | * Do this with the "cpath wpath" cause we might create the file 463 | * in doing so. 464 | */ 465 | 466 | c = asprintf(&cp, "%s/.slant-errlog", getenv("HOME")); 467 | if (c < 0) 468 | err(EXIT_FAILURE, NULL); 469 | if (NULL == (out.errs = fopen(cp, "a"))) 470 | err(EXIT_FAILURE, "%s", cp); 471 | free(cp); 472 | 473 | /* Repledge by dropping the error log pledges. */ 474 | 475 | #if HAVE_PLEDGE 476 | if (-1 == pledge("tty rpath dns inet stdio", NULL)) 477 | err(EXIT_FAILURE, NULL); 478 | #endif 479 | 480 | /* Start up TLS handling really early. */ 481 | 482 | if (tls_init() < 0) 483 | err(EXIT_FAILURE, NULL); 484 | 485 | /* 486 | * Establish our signal handling: have TERM, QUIT, and INT 487 | * interrupt the poll and cause us to exit. 488 | * Otherwise, we block the signal. 489 | * TODO: handle SINGWINCH. 490 | */ 491 | 492 | if (sigemptyset(&mask) < 0) 493 | err(EXIT_FAILURE, NULL); 494 | if (SIG_ERR == signal(SIGTERM, dosig)) 495 | err(EXIT_FAILURE, NULL); 496 | if (SIG_ERR == signal(SIGQUIT, dosig)) 497 | err(EXIT_FAILURE, NULL); 498 | if (SIG_ERR == signal(SIGINT, dosig)) 499 | err(EXIT_FAILURE, NULL); 500 | if (sigaddset(&mask, SIGTERM) < 0) 501 | err(EXIT_FAILURE, NULL); 502 | if (sigaddset(&mask, SIGQUIT) < 0) 503 | err(EXIT_FAILURE, NULL); 504 | if (sigaddset(&mask, SIGINT) < 0) 505 | err(EXIT_FAILURE, NULL); 506 | if (sigprocmask(SIG_BLOCK, &mask, &oldmask) < 0) 507 | err(EXIT_FAILURE, NULL); 508 | 509 | /* Parse arguments. */ 510 | 511 | while (-1 != (c = getopt(argc, argv, "f:o:w:"))) 512 | switch (c) { 513 | case 'f': 514 | cfgfile = strdup(optarg); 515 | break; 516 | case 'o': 517 | if (0 == strcmp(optarg, "host")) 518 | d.order = DRAWORD_HOST; 519 | else if (0 == strcmp(optarg, "cmdline")) 520 | d.order = DRAWORD_CMDLINE; 521 | else if (0 == strcmp(optarg, "cpu")) 522 | d.order = DRAWORD_CPU; 523 | else if (0 == strcmp(optarg, "mem")) 524 | d.order = DRAWORD_MEM; 525 | else 526 | goto usage; 527 | break; 528 | default: 529 | goto usage; 530 | } 531 | 532 | argc -= optind; 533 | argv += optind; 534 | 535 | /* 536 | * Parse our configuration file. 537 | * This will tell us all we need to know about our runtime. 538 | * Use "cp" as a temporary variable in case we need to create 539 | * the configuration file name on-demand. 540 | */ 541 | 542 | cp = NULL; 543 | if (NULL == cfgfile) { 544 | c = asprintf(&cp, "%s/.slantrc", getenv("HOME")); 545 | if (c < 0) 546 | err(EXIT_FAILURE, NULL); 547 | cfgfile = cp; 548 | } 549 | 550 | c = config_parse(cfgfile, &cfg, argc, argv); 551 | free(cp); 552 | if ( ! c) 553 | return EXIT_FAILURE; 554 | 555 | /* 556 | * Initialise data. 557 | * On any failure---which in this block will be memory 558 | * failure---just exit the program immediately. 559 | * We don't really have any state to clean up at this point. 560 | */ 561 | 562 | if (0 == cfg.urlsz) 563 | errx(EXIT_FAILURE, "no urls given"); 564 | 565 | n = calloc(cfg.urlsz, sizeof(struct node)); 566 | if (NULL == n) 567 | err(EXIT_FAILURE, NULL); 568 | 569 | pfds = calloc(cfg.urlsz, sizeof(struct pollfd)); 570 | if (NULL == pfds) 571 | err(EXIT_FAILURE, NULL); 572 | 573 | for (i = 0; i < cfg.urlsz; i++) { 574 | pfds[i].fd = -1; 575 | n[i].xfer.pfd = &pfds[i]; 576 | n[i].state = STATE_STARTUP; 577 | n[i].url = cfg.urls[i].url; 578 | n[i].waittime = 579 | cfg.urls[i].waittime ? 580 | cfg.urls[i].waittime : (time_t)cfg.waittime; 581 | n[i].timeout = 582 | cfg.urls[i].timeout ? 583 | cfg.urls[i].timeout : (time_t)cfg.timeout; 584 | dns_parse_url(&out, &n[i]); 585 | } 586 | 587 | /* 588 | * All data initialised. 589 | * Get our window system ready to roll. 590 | * Once we initialise the screen, we're going to need to use our 591 | * "errs" and "errwin" to report errors, as stderr will just 592 | * munge on the screen. 593 | */ 594 | 595 | if (NULL == initscr() || 596 | ERR == start_color() || 597 | ERR == cbreak() || 598 | ERR == noecho() || 599 | ERR == nonl()) 600 | exit(EXIT_FAILURE); 601 | 602 | curs_set(0); 603 | init_pair(1, COLOR_YELLOW, COLOR_BLACK); 604 | init_pair(2, COLOR_RED, COLOR_BLACK); 605 | getmaxyx(stdscr, maxy, maxx); 606 | 607 | /* Configure what we see, bailing if it's not possible. */ 608 | 609 | c = layout(&cfg, &out, maxx, maxy, n, cfg.urlsz, &d); 610 | if (c < 0) { 611 | endwin(); 612 | warn(NULL); 613 | goto out; 614 | } else if (0 == c) { 615 | endwin(); 616 | warnx("insufficient screen dimensions"); 617 | goto out; 618 | } 619 | 620 | assert((size_t)maxy > d.errlog); 621 | out.mainwin = subwin(stdscr, maxy - d.errlog, maxx, 0, 0); 622 | if (d.errlog) { 623 | out.errwin = subwin(stdscr, 0, maxx, maxy - d.errlog, 0); 624 | scrollok(out.errwin, 1); 625 | } 626 | 627 | for (i = 0; i < cfg.urlsz; i++) { 628 | n[i].state = STATE_RESOLVING; 629 | if ( ! dns_resolve(&out, n[i].host, &n[i].addrs)) 630 | goto out; 631 | n[i].state = STATE_CONNECT_READY; 632 | } 633 | 634 | /* 635 | * FIXME: rpath needed by libressl. 636 | * We can relieve this by pre-loading our certs. 637 | */ 638 | 639 | #if HAVE_PLEDGE 640 | if (-1 == pledge("tty rpath inet stdio", NULL)) 641 | err(EXIT_FAILURE, NULL); 642 | #endif 643 | 644 | /* Main loop. */ 645 | 646 | ts.tv_sec = 1; 647 | ts.tv_nsec = 0; 648 | last = 0; 649 | 650 | while ( ! sigged) { 651 | now = time(NULL); 652 | if ((c = nodes_update(&out, n, cfg.urlsz, now)) < 0) 653 | break; 654 | 655 | /* Re-sort, if applicable. */ 656 | 657 | sz = sizeof(struct node); 658 | switch (d.order) { 659 | case DRAWORD_CMDLINE: 660 | break; 661 | case DRAWORD_CPU: 662 | qsort(n, cfg.urlsz, sz, cmp_cpu); 663 | break; 664 | case DRAWORD_HOST: 665 | /* Only do this once. */ 666 | if (0 == first) 667 | break; 668 | qsort(n, cfg.urlsz, sz, cmp_host); 669 | break; 670 | case DRAWORD_MEM: 671 | qsort(n, cfg.urlsz, sz, cmp_mem); 672 | break; 673 | } 674 | 675 | /* 676 | * Update if our data is dirty (c > 0) or if we're on 677 | * the first iteration, just to show something. 678 | * If we've nothing to show but more than one second has 679 | * passed, then simply update the time displays. 680 | */ 681 | 682 | if ((c || first) && now > last) { 683 | draw(&out, &d, n, cfg.urlsz, now); 684 | for (i = 0; i < cfg.urlsz; i++) 685 | n[i].dirty = 0; 686 | wrefresh(out.mainwin); 687 | first = 0; 688 | } else if (now > last) { 689 | drawtimes(&out, &d, n, cfg.urlsz, now); 690 | wrefresh(out.mainwin); 691 | } 692 | 693 | last = now; 694 | if (ppoll(pfds, cfg.urlsz, &ts, &oldmask) < 0 && 695 | EINTR != errno) { 696 | xwarn(&out, "poll"); 697 | break; 698 | } 699 | } 700 | 701 | out: 702 | if ( ! isendwin()) { 703 | if (NULL != out.errwin) 704 | delwin(out.errwin); 705 | delwin(out.mainwin); 706 | endwin(); 707 | } 708 | nodes_free(n, cfg.urlsz); 709 | config_free(&cfg); 710 | free(d.box); 711 | free(pfds); 712 | return EXIT_SUCCESS; 713 | usage: 714 | fprintf(stderr, "usage: %s " 715 | "[-f conf] " 716 | "[-o order] " 717 | "[url...]\n", 718 | getprogname()); 719 | return EXIT_FAILURE; 720 | } 721 | -------------------------------------------------------------------------------- /slant-collectd-linux.c: -------------------------------------------------------------------------------- 1 | #ifdef __linux__ 2 | /* $Id$ */ 3 | /* 4 | * A lot of this file is a restatement of OpenBSD's top(1) machine.c. 5 | * Its copyright and license file is retained below. 6 | */ 7 | /*- 8 | * Copyright (c) 1994 Thorsten Lockert 9 | * Copyright (c) 2018 Thorsten Lockert 10 | * All rights reserved. 11 | * 12 | * Redistribution and use in source and binary forms, with or without 13 | * modification, are permitted provided that the following conditions 14 | * are met: 15 | * 1. Redistributions of source code must retain the above copyright 16 | * notice, this list of conditions and the following disclaimer. 17 | * 2. Redistributions in binary form must reproduce the above copyright 18 | * notice, this list of conditions and the following disclaimer in the 19 | * documentation and/or other materials provided with the distribution. 20 | * 3. The name of the author may not be used to endorse or promote products 21 | * derived from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 24 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 25 | * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 26 | * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 27 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 28 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 29 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 31 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 32 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | /* 35 | * Other parts are from OpenBSD's systat(1) if.c. 36 | * Its copyright and license file is retained below. 37 | */ 38 | /* 39 | * Copyright (c) 2004 Markus Friedl 40 | * Copyright (c) 2018 Kristaps Dzonsons 41 | * Copyright (c) 2018 Duncan Overbruck 42 | * 43 | * Permission to use, copy, modify, and distribute this software for any 44 | * purpose with or without fee is hereby granted, provided that the above 45 | * copyright notice and this permission notice appear in all copies. 46 | * 47 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 48 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 49 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 50 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 51 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 52 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 53 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 54 | */ 55 | #include "config.h" 56 | 57 | #include 58 | 59 | #include 60 | #include 61 | #include 62 | #include 63 | 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | 78 | #include "slant-collectd.h" 79 | #include "extern.h" 80 | #include "db.h" 81 | 82 | /* 83 | * /proc/stat 84 | */ 85 | #define CP_USER 0 86 | #define CP_NICE 1 87 | #define CP_SYS 2 88 | #define CP_IDLE 3 89 | #define CP_IOWAIT 4 90 | #define CP_IRQ 5 91 | #define CP_SOFTIRQ 6 92 | #define CP_STEAL 7 93 | #define CP_GUEST 8 94 | #define CP_GUEST_NICE 9 95 | #define CPUSTATES 10 96 | 97 | /* 98 | * Sector size is always 512 99 | * https://lkml.org/lkml/2015/8/17/269 100 | */ 101 | #define SECTOR_SHIFT 9 102 | 103 | struct ifcount { 104 | uint64_t ifc_ib; /* input bytes */ 105 | uint64_t ifc_ip; /* input packets */ 106 | uint64_t ifc_ie; /* input errors */ 107 | uint64_t ifc_ob; /* output bytes */ 108 | uint64_t ifc_op; /* output packets */ 109 | uint64_t ifc_oe; /* output errors */ 110 | uint64_t ifc_co; /* collisions */ 111 | }; 112 | 113 | struct ifstat { 114 | struct ifcount ifs_cur; 115 | struct ifcount ifs_old; 116 | struct ifcount ifs_now; 117 | }; 118 | 119 | struct sysinfo { 120 | size_t sample; /* sample number */ 121 | double mem_avg; /* average memory */ 122 | double nproc_pct; /* nprocs percent */ 123 | double nfile_pct; /* nfiles percent */ 124 | uint64_t cpu_states[CPUSTATES]; /* used for cpu compute */ 125 | double cpu_avg; /* average cpu */ 126 | uint64_t cp_time[CPUSTATES]; /* used for cpu compute */ 127 | uint64_t cp_old[CPUSTATES]; /* used for cpu compute */ 128 | uint64_t cp_diff[CPUSTATES]; /* used for cpu compute */ 129 | double rproc_pct; /* pct command (by name) found */ 130 | struct ifstat *ifstats; /* used for inet compute */ 131 | size_t ifstatsz; /* used for inet compute */ 132 | struct ifcount ifsum; /* average inet */ 133 | u_int64_t disc_rbytes; /* last disc total read */ 134 | u_int64_t disc_wbytes; /* last disc total write */ 135 | int64_t disc_ravg; /* average reads/sec */ 136 | int64_t disc_wavg; /* average reads/sec */ 137 | time_t boottime; /* time booted */ 138 | }; 139 | 140 | static void 141 | percentages(int cnt, uint64_t *out, 142 | uint64_t *new, uint64_t *old, uint64_t *diffs) 143 | { 144 | uint64_t change, tot, *dp, half_total; 145 | int i; 146 | 147 | /* initialization */ 148 | 149 | tot = 0; 150 | dp = diffs; 151 | 152 | /* calculate changes for each state and the overall change */ 153 | 154 | for (i = 0; i < cnt; i++) { 155 | if ((int64_t)(change = *new - *old) < 0) { 156 | /* this only happens when the counter wraps */ 157 | change = INT64_MAX - *old + *new; 158 | } 159 | tot += (*dp++ = change); 160 | *old++ = *new++; 161 | } 162 | 163 | /* avoid divide by zero potential */ 164 | 165 | if (tot == 0) 166 | tot = 1; 167 | 168 | /* calculate percentages based on overall change, rounding up */ 169 | 170 | half_total = tot / 2l; 171 | for (i = 0; i < cnt; i++) 172 | *out++ = ((*diffs++ * 1000 + half_total) / tot); 173 | } 174 | 175 | void 176 | sysinfo_free(struct sysinfo *p) 177 | { 178 | if (NULL == p) 179 | return; 180 | 181 | free(p->ifstats); 182 | free(p); 183 | } 184 | 185 | static int 186 | sysinfo_init_boottime(struct sysinfo *p) 187 | { 188 | FILE *fp; 189 | char *line = NULL; 190 | size_t n = 0; 191 | uint64_t btime = 0; 192 | 193 | if ((fp = fopen("/proc/stat", "r")) == NULL) { 194 | warn("open: /proc/stat"); 195 | return 0; 196 | } 197 | 198 | while (-1 != getline(&line, &n, fp)) { 199 | if (0 == strcmp("btime ", line)) { 200 | if (1 != scanf(line+6, "%" SCNu64, &btime)) { 201 | warnx("failed to get boot time"); 202 | fclose(fp); 203 | free(line); 204 | return 0; 205 | } 206 | break; 207 | } 208 | } 209 | 210 | fclose(fp); 211 | free(line); 212 | 213 | p->boottime = btime; 214 | 215 | return 1; 216 | } 217 | 218 | struct sysinfo * 219 | sysinfo_alloc(void) 220 | { 221 | struct sysinfo *p; 222 | 223 | p = calloc(1, sizeof(struct sysinfo)); 224 | if (NULL == p) { 225 | warn(NULL); 226 | return NULL; 227 | } 228 | 229 | if ( ! sysinfo_init_boottime(p)) { 230 | sysinfo_free(p); 231 | return NULL; 232 | } 233 | 234 | return p; 235 | } 236 | 237 | static char buf[8192]; 238 | 239 | static ssize_t 240 | proc_read_buf(const char *file) 241 | { 242 | int fd; 243 | ssize_t rd; 244 | if (-1 == (fd = open(file, O_RDONLY))) { 245 | warn("open: %s", file); 246 | return -1; 247 | } 248 | if (-1 == (rd = read(fd, buf, sizeof buf - 1))) { 249 | warn("read: %s", file); 250 | close(fd); 251 | return -1; 252 | } 253 | close(fd); 254 | buf[rd] = '\0'; 255 | #ifdef DEBUG 256 | warnx("%s: read %ld bytes", file, rd); 257 | #endif 258 | return rd; 259 | } 260 | 261 | static int 262 | sysinfo_update_mem(struct sysinfo *p) 263 | { 264 | ssize_t rd; 265 | uint64_t memtotal, memfree; 266 | char *ptr; 267 | 268 | rd = proc_read_buf("/proc/meminfo"); 269 | if (-1 == rd) 270 | return 0; 271 | 272 | if (NULL == (ptr = memmem(buf, rd, "MemTotal:", 9))) 273 | goto errparse; 274 | if (1 != sscanf(ptr+9, "%" SCNu64, &memtotal)) 275 | goto errparse; 276 | 277 | if (NULL == (ptr = memmem(buf, rd, "MemFree:", 8))) 278 | goto errparse; 279 | if (1 != sscanf(ptr+8, "%" SCNu64, &memfree)) 280 | goto errparse; 281 | 282 | p->mem_avg = 100.0 * (memtotal - memfree) / memtotal; 283 | 284 | #ifdef DEBUG 285 | warnx("memtotal=%ld memfree=%ld mem_avg=%lf", memtotal, memfree, p->mem_avg); 286 | #endif 287 | 288 | return 1; 289 | errparse: 290 | warnx("error while parsing /proc/meminfo"); 291 | return 0; 292 | } 293 | 294 | static int 295 | sysinfo_update_nfiles(const struct syscfg *cfg, struct sysinfo *p) 296 | { 297 | uint64_t allocfiles, unusedfiles, maxfiles; 298 | ssize_t rd; 299 | 300 | rd = proc_read_buf("/proc/sys/fs/file-nr"); 301 | if (-1 == rd) 302 | return 0; 303 | 304 | if (3 != sscanf(buf, "%" SCNu64 " %" SCNu64 " %" SCNu64, 305 | &allocfiles, &unusedfiles, &maxfiles)) { 306 | warnx("failed to parse /proc/sys/fs/file-nr"); 307 | return 0; 308 | } 309 | 310 | #ifdef DEBUG 311 | warnx("allocfiles=%ld maxfiles=%ld", allocfiles, maxfiles); 312 | #endif 313 | 314 | p->nfile_pct = 100.0 * allocfiles / (double)maxfiles; 315 | return 1; 316 | } 317 | 318 | static int 319 | sysinfo_update_nprocs(const struct syscfg *cfg, struct sysinfo *p) 320 | { 321 | struct dirent *dent; 322 | DIR *dir; 323 | uint64_t maxproc, nprocs = 0; 324 | ssize_t rd; 325 | 326 | rd = proc_read_buf("/proc/sys/kernel/pid_max"); 327 | if (-1 == rd) 328 | return 0; 329 | 330 | if (1 != sscanf(buf, "%" SCNu64, &maxproc)) { 331 | warnx("error while parsing /proc/sys/kernel/pid_max"); 332 | return 0; 333 | } 334 | 335 | dir = opendir("/proc"); 336 | if (NULL == dir) { 337 | warn("opendir: /proc"); 338 | return 0; 339 | } 340 | 341 | while (NULL != (dent = readdir(dir))) { 342 | if (isdigit(*dent->d_name)) 343 | nprocs++; 344 | } 345 | closedir(dir); 346 | 347 | #ifdef DEBUG 348 | warnx("procs: nprocs=%" PRIu64 " maxproc=%" PRIu64, nprocs, maxproc); 349 | #endif 350 | 351 | p->nproc_pct = 100.0 * nprocs / (double)maxproc; 352 | 353 | /* 354 | * FIXME: add rproc support 355 | */ 356 | p->rproc_pct = .0; 357 | 358 | return 1; 359 | } 360 | 361 | static int 362 | sysinfo_update_cpu(struct sysinfo *p) 363 | { 364 | int64_t val; 365 | ssize_t rd; 366 | 367 | rd = proc_read_buf("/proc/stat"); 368 | if (-1 == rd) 369 | return 0; 370 | 371 | if (10 != sscanf(buf, "cpu" 372 | " %" SCNu64 373 | " %" SCNu64 374 | " %" SCNu64 375 | " %" SCNu64 376 | " %" SCNu64 377 | " %" SCNu64 378 | " %" SCNu64 379 | " %" SCNu64 380 | " %" SCNu64 381 | " %" SCNu64, 382 | &p->cp_time[0], 383 | &p->cp_time[1], 384 | &p->cp_time[2], 385 | &p->cp_time[3], 386 | &p->cp_time[4], 387 | &p->cp_time[5], 388 | &p->cp_time[6], 389 | &p->cp_time[7], 390 | &p->cp_time[8], 391 | &p->cp_time[9])) { 392 | warnx("error while parsing /proc/stat"); 393 | return 0; 394 | } 395 | 396 | percentages(CPUSTATES, p->cpu_states, &p->cp_time[0], 397 | &p->cp_old[0], &p->cp_diff[0]); 398 | 399 | #ifdef DEBUG 400 | for (int i = 0; i < CPUSTATES; i++) 401 | warnx("%d: cp_time=%ld cp_old=%ld cp_diff=%ld", 402 | i, p->cp_time[i], p->cp_old[i], p->cp_diff[i]); 403 | #endif 404 | 405 | /* Update our averages. */ 406 | 407 | val = 1000 - p->cpu_states[CP_IDLE]; 408 | if (val > 1000) { 409 | warnx("CPU state out of bound: %" PRId64, val); 410 | val = 1000; 411 | } else if (val < 0) { 412 | warnx("CPU state out of bound: %" PRId64, val); 413 | val = 0; 414 | } 415 | 416 | p->cpu_avg = val / 10.; 417 | 418 | return 1; 419 | } 420 | 421 | static int 422 | get_ifflags(int *fd, const char *ifname, short *ifflags) 423 | { 424 | static struct ifreq ifr; 425 | 426 | if (-1 == *fd && 427 | -1 == (*fd = socket(AF_UNIX, SOCK_DGRAM, 0))) { 428 | warn("socket"); 429 | return 0; 430 | } 431 | 432 | if (strlcpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name)) >= 433 | sizeof (ifr.ifr_name)) { 434 | warnx("ifname too long"); 435 | return 0; 436 | } 437 | 438 | if (-1 == ioctl(*fd, SIOCGIFFLAGS, &ifr)) { 439 | warn("ioctl: SIOCGIFFLAGS"); 440 | return 0; 441 | } 442 | 443 | *ifflags = ifr.ifr_flags; 444 | 445 | return 1; 446 | } 447 | 448 | static int 449 | get_ifindex(struct if_nameindex *idx, const char *ifname, int *ifindex) 450 | { 451 | struct if_nameindex *next; 452 | for (next = idx; (next->if_index && next->if_name); next++) { 453 | if (0 == strcmp(ifname, next->if_name)) { 454 | *ifindex = next->if_index; 455 | return 1; 456 | } 457 | } 458 | return 0; 459 | } 460 | 461 | #define UPDATE(x, y, up) \ 462 | do { \ 463 | ifs->ifs_now.x = y; \ 464 | ifs->ifs_cur.x = ifs->ifs_now.x - ifs->ifs_old.x; \ 465 | ifs->ifs_old.x = ifs->ifs_now.x; \ 466 | ifs->ifs_cur.x /= 15; \ 467 | if ((up)) \ 468 | p->ifsum.x += ifs->ifs_cur.x; \ 469 | } while(0) 470 | 471 | static int 472 | sysinfo_update_if(struct sysinfo *p) 473 | { 474 | struct ifcount ifctmp; 475 | struct ifstat *newstats, *ifs; 476 | struct if_nameindex *idx; 477 | char *ptr, *ifname; 478 | ssize_t rd; 479 | int ifindex, up, sockfd = -1; 480 | short flags; 481 | 482 | idx = if_nameindex(); 483 | if (NULL == idx) 484 | return 0; 485 | 486 | rd = proc_read_buf("/proc/net/dev"); 487 | if (-1 == rd) 488 | goto err; 489 | 490 | /* 491 | * Skip two header lines 492 | */ 493 | if (NULL == (ptr = strchr(buf, '\n')) || 494 | NULL == (ptr = strchr(ptr+1, '\n')) || 495 | '\0' == *++ptr) 496 | goto errparse; 497 | 498 | 499 | memset(&p->ifsum, 0, sizeof(p->ifsum)); 500 | 501 | for (; ptr < buf+rd; ptr++) { 502 | /* 503 | * Get interface name, skip leading spaces 504 | */ 505 | for (;*ptr == ' '; ptr++) ; 506 | ifname = ptr; 507 | if (NULL == (ptr = strchr(ptr, ':'))) 508 | goto errparse; 509 | *ptr++ = '\0'; 510 | 511 | if ( ! get_ifindex(idx, ifname, &ifindex)) { 512 | warnx("couldn't find ifindex for '%s'", ifname); 513 | goto err; 514 | } 515 | 516 | sscanf(ptr, 517 | " %" SCNu64 // rx-bytes 518 | " %" SCNu64 // rx-packets 519 | " %" SCNu64 // rx-errors 520 | " %*u" // rx-drop 521 | " %*u" // rx-fifo 522 | " %*u" // rx-frame 523 | " %*u" // rx-compressed 524 | " %*u" // rx-multicast 525 | " %" SCNu64 // tx-bytes 526 | " %" SCNu64 // tx-packates 527 | " %" SCNu64 // tx-errors 528 | " %*u" // tx-drop 529 | " %*u" // tx-fifo 530 | " %" SCNu64 // tx-collisions 531 | " %*u" // tx-carrier 532 | " %*u" , // tx-compressed 533 | &ifctmp.ifc_ib, 534 | &ifctmp.ifc_ip, 535 | &ifctmp.ifc_ie, 536 | &ifctmp.ifc_ob, 537 | &ifctmp.ifc_op, 538 | &ifctmp.ifc_oe, 539 | &ifctmp.ifc_co); 540 | 541 | if ( ! get_ifflags(&sockfd, ifname, &flags)) 542 | goto err; 543 | 544 | if ((unsigned int)ifindex >= p->ifstatsz) { 545 | newstats = reallocarray 546 | (p->ifstats, ifindex + 4, 547 | sizeof(struct ifstat)); 548 | if (NULL == newstats) { 549 | warn(NULL); 550 | goto err; 551 | } 552 | p->ifstats = newstats; 553 | while (p->ifstatsz < (unsigned int)ifindex + 4) { 554 | memset(&p->ifstats[p->ifstatsz], 555 | 0, sizeof(*p->ifstats)); 556 | p->ifstatsz++; 557 | } 558 | } 559 | ifs = &p->ifstats[ifindex]; 560 | 561 | /* Only consider non-loopback up addresses. */ 562 | 563 | up = (flags & IFF_UP) && ! (flags & IFF_LOOPBACK); 564 | 565 | #ifdef DEBUG 566 | warnx("%s: ifindex=%d up=%d", ifname, ifindex, up); 567 | #endif 568 | 569 | UPDATE(ifc_ip, ifctmp.ifc_ip, up); 570 | UPDATE(ifc_ib, ifctmp.ifc_ib, up); 571 | UPDATE(ifc_ie, ifctmp.ifc_ie, up); 572 | UPDATE(ifc_op, ifctmp.ifc_op, up); 573 | UPDATE(ifc_ob, ifctmp.ifc_ob, up); 574 | UPDATE(ifc_oe, ifctmp.ifc_oe, up); 575 | UPDATE(ifc_co, ifctmp.ifc_co, up); 576 | 577 | if (NULL == (ptr = strchr(ptr, '\n'))) 578 | goto errparse; 579 | } 580 | 581 | close(sockfd); 582 | if_freenameindex(idx); 583 | return 1; 584 | errparse: 585 | warnx("error while parsing /proc/net/dev"); 586 | err: 587 | close(sockfd); 588 | if_freenameindex(idx); 589 | return 0; 590 | } 591 | 592 | static int 593 | is_real_block_device(char *name) 594 | { 595 | char path[PATH_MAX]; 596 | char *p; 597 | 598 | /* 599 | * iostat replaces slashes in device names with `!` 600 | */ 601 | p = name; 602 | while (NULL != (p = strchr(p, '/'))) 603 | *p = '!'; 604 | 605 | /* 606 | * real devices have the `device` symlink in their 607 | * `/sys/block/` directory. 608 | */ 609 | snprintf(path, sizeof path, "/sys/block/%s/device", name); 610 | return 0 == access(path, F_OK); 611 | } 612 | 613 | static int 614 | sysinfo_update_disc(const struct syscfg *cfg, struct sysinfo *p) 615 | { 616 | ssize_t rd; 617 | char name[128]; 618 | unsigned long secrd, secwr; 619 | char *ptr; 620 | uint64_t rs = 0, ws = 0; 621 | uint64_t rb = 0, wb = 0; 622 | 623 | rd = proc_read_buf("/proc/diskstats"); 624 | if (-1 == rd) 625 | return 0; 626 | 627 | for (ptr = buf; ptr < buf+rd; ptr++) { 628 | /* 629 | * field 3 = device name 630 | * field 6 = sectors read 631 | * field 10 = sectors written 632 | */ 633 | if (3 != sscanf(ptr, "%*u %*u %127s " 634 | "%*u %*u %lu %*u %*u %*u %lu %*u %*u %*u %*u", 635 | name, &secrd, &secwr)) 636 | goto errparse; 637 | if (NULL == (ptr = strchr(ptr, '\n'))) 638 | goto errparse; 639 | if ( ! is_real_block_device(name)) 640 | continue; 641 | rs += secrd; 642 | ws += secwr; 643 | } 644 | 645 | rb = rs << SECTOR_SHIFT; 646 | wb = ws << SECTOR_SHIFT; 647 | 648 | if (rb > p->disc_rbytes) { 649 | p->disc_ravg = (rb - p->disc_rbytes) / 15; 650 | p->disc_rbytes = rb; 651 | } else { 652 | p->disc_ravg = 0; 653 | p->disc_rbytes = rb; 654 | } 655 | 656 | if (wb > p->disc_wbytes) { 657 | p->disc_wavg = (wb - p->disc_wbytes) / 15; 658 | p->disc_wbytes = wb; 659 | } else { 660 | p->disc_wavg = 0; 661 | p->disc_wbytes = wb; 662 | } 663 | 664 | #ifdef DEBUG 665 | warnx("disc: rbytes=%lu wbytes=%lu", p->disc_rbytes, p->disc_rbytes); 666 | #endif 667 | 668 | return 1; 669 | errparse: 670 | warnx("error while parsing /proc/diskstats"); 671 | return 0; 672 | } 673 | 674 | int 675 | sysinfo_update(const struct syscfg *cfg, struct sysinfo *p) 676 | { 677 | if ( ! sysinfo_update_nprocs(cfg, p)) 678 | return 0; 679 | if ( ! sysinfo_update_nfiles(cfg, p)) 680 | return 0; 681 | if ( ! sysinfo_update_cpu(p)) 682 | return 0; 683 | if ( ! sysinfo_update_mem(p)) 684 | return 0; 685 | if ( ! sysinfo_update_if(p)) 686 | return 0; 687 | if ( ! sysinfo_update_disc(cfg, p)) 688 | return 0; 689 | 690 | p->sample++; 691 | return 1; 692 | } 693 | 694 | double 695 | sysinfo_get_cpu_avg(const struct sysinfo *p) 696 | { 697 | 698 | return p->cpu_avg; 699 | } 700 | 701 | double 702 | sysinfo_get_mem_avg(const struct sysinfo *p) 703 | { 704 | 705 | return p->mem_avg; 706 | } 707 | 708 | int64_t 709 | sysinfo_get_nettx_avg(const struct sysinfo *p) 710 | { 711 | 712 | if (1 == p->sample) 713 | return 0; 714 | return p->ifsum.ifc_ob; 715 | } 716 | 717 | int64_t 718 | sysinfo_get_netrx_avg(const struct sysinfo *p) 719 | { 720 | 721 | if (1 == p->sample) 722 | return 0; 723 | return p->ifsum.ifc_ib; 724 | } 725 | 726 | int64_t 727 | sysinfo_get_discread_avg(const struct sysinfo *p) 728 | { 729 | 730 | if (1 == p->sample) 731 | return 0; 732 | return p->disc_ravg; 733 | } 734 | 735 | int64_t 736 | sysinfo_get_discwrite_avg(const struct sysinfo *p) 737 | { 738 | 739 | if (1 == p->sample) 740 | return 0; 741 | return p->disc_wavg; 742 | } 743 | 744 | double 745 | sysinfo_get_rprocs(const struct sysinfo *p) 746 | { 747 | 748 | return p->rproc_pct; 749 | } 750 | 751 | double 752 | sysinfo_get_nfiles(const struct sysinfo *p) 753 | { 754 | 755 | return p->nfile_pct; 756 | } 757 | 758 | double 759 | sysinfo_get_nprocs(const struct sysinfo *p) 760 | { 761 | 762 | return p->nproc_pct; 763 | } 764 | 765 | time_t 766 | sysinfo_get_boottime(const struct sysinfo *p) 767 | { 768 | 769 | return p->boottime; 770 | } 771 | #endif 772 | -------------------------------------------------------------------------------- /slant-config.c: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | /* 3 | * Copyright (c) 2018 Kristaps Dzonsons 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | #include "config.h" 18 | 19 | #if HAVE_SYS_QUEUE 20 | # include 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #if HAVE_ERR 30 | # include 31 | #endif 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "extern.h" 40 | #include "slant.h" 41 | 42 | struct parse { 43 | const char *fn; /* current parse function */ 44 | const char **toks; /* list of all tokens */ 45 | size_t toksz; /* number of tokens */ 46 | size_t pos; /* position in tokens */ 47 | }; 48 | 49 | static int 50 | tok_unknown(const struct parse *p) 51 | { 52 | 53 | warnx("%s: unknown token: \"%s\"", 54 | p->fn, p->toks[p->pos]); 55 | return 0; 56 | } 57 | 58 | /* 59 | * Check whether the current token exists, i.e., whether we haven't 60 | * fallen into the EOF. 61 | * Returns zero on (and logs the) failure (EOF), non-zero otherwise. 62 | */ 63 | static int 64 | tok_nadv(const struct parse *p) 65 | { 66 | 67 | if (p->pos >= p->toksz) { 68 | warnx("%s: unexpected eof", p->fn); 69 | return 0; 70 | } 71 | return 1; 72 | } 73 | 74 | /* 75 | * Expect a given token "v" at the current parse point. 76 | * Check if that's the case. 77 | * Returns zero (and logs) if it is not; non-zero otherwise. 78 | */ 79 | static int 80 | tok_expect(const struct parse *p, const char *v) 81 | { 82 | 83 | if ( ! tok_nadv(p)) 84 | return 0; 85 | if (0 == strcmp(p->toks[p->pos], v)) 86 | return 1; 87 | warnx("%s: expected \"%s\", have \"%s\"", 88 | p->fn, v, p->toks[p->pos]); 89 | return 0; 90 | } 91 | 92 | /* 93 | * Just tok_expect(), but also advancing on success. 94 | */ 95 | static int 96 | tok_expect_adv(struct parse *p, const char *v) 97 | { 98 | 99 | if (tok_expect(p, v)) { 100 | p->pos++; 101 | return 1; 102 | } 103 | return 0; 104 | } 105 | 106 | /* 107 | * Test whether the current token equals "v". 108 | * Returns zero on failure (no match), non-zero on success. 109 | */ 110 | static int 111 | tok_eq(const struct parse *p, const char *v) 112 | { 113 | 114 | assert(p->pos < p->toksz); 115 | return 0 == strcmp(p->toks[p->pos], v); 116 | } 117 | 118 | /* 119 | * Like tok_eq(), but advanced on success. 120 | */ 121 | static int 122 | tok_eq_adv(struct parse *p, const char *v) 123 | { 124 | 125 | assert(p->pos < p->toksz); 126 | if (0 == strcmp(p->toks[p->pos], v)) { 127 | p->pos++; 128 | return 1; 129 | } 130 | return 0; 131 | } 132 | 133 | /* 134 | * Advance to the next position then call tok_nadv(). 135 | * See tok_nadv() for return values and details. 136 | */ 137 | static int 138 | tok_adv(struct parse *p) 139 | { 140 | 141 | ++p->pos; 142 | return tok_nadv(p); 143 | } 144 | 145 | /* 146 | * "timeout" num ";" 147 | */ 148 | static int 149 | parse_timeout(struct parse *p, struct config *cfg) 150 | { 151 | const char *er; 152 | 153 | assert(p->pos < p->toksz); 154 | cfg->timeout = strtonum 155 | (p->toks[p->pos], 10, INT_MAX, &er); 156 | if (NULL != er) { 157 | warnx("%s: bad global timeout: %s", p->fn, er); 158 | return 0; 159 | } else if ( ! tok_adv(p)) { 160 | return 0; 161 | } else if ( ! tok_expect_adv(p, ";")) 162 | return 0; 163 | 164 | return 1; 165 | } 166 | 167 | /* 168 | * "waittime" num ";" 169 | */ 170 | static int 171 | parse_waittime(struct parse *p, struct config *cfg) 172 | { 173 | const char *er; 174 | 175 | assert(p->pos < p->toksz); 176 | cfg->waittime = strtonum 177 | (p->toks[p->pos], 15, INT_MAX, &er); 178 | if (NULL != er) { 179 | warnx("%s: bad global waittime: %s", p->fn, er); 180 | return 0; 181 | } else if ( ! tok_adv(p)) { 182 | return 0; 183 | } else if ( ! tok_expect_adv(p, ";")) 184 | return 0; 185 | 186 | return 1; 187 | } 188 | 189 | /* 190 | * ["waittime" num | "timeout" num] "}" 191 | */ 192 | static int 193 | parse_server_args(struct parse *p, struct config *cfg, size_t count) 194 | { 195 | const char *er; 196 | time_t waittime = 0, timeout = 0; 197 | size_t i, j; 198 | 199 | while (p->pos < p->toksz && ! tok_eq(p, "}")) { 200 | if (tok_eq_adv(p, "waittime")) { 201 | waittime = strtonum 202 | (p->toks[p->pos], 15, INT_MAX, &er); 203 | if (NULL != er) { 204 | warnx("%s: bad server waittime: " 205 | "%s", p->fn, er); 206 | return 0; 207 | } else if ( ! tok_adv(p)) 208 | return 0; 209 | } else if (tok_eq_adv(p, "timeout")) { 210 | timeout = strtonum 211 | (p->toks[p->pos], 10, INT_MAX, &er); 212 | if (NULL != er) { 213 | warnx("%s: bad server timeout: " 214 | "%s", p->fn, er); 215 | return 0; 216 | } else if ( ! tok_adv(p)) 217 | return 0; 218 | } else 219 | return tok_unknown(p); 220 | 221 | (void)tok_eq_adv(p, ";"); 222 | } 223 | 224 | if ( ! tok_expect_adv(p, "}")) 225 | return 0; 226 | 227 | assert(cfg->urlsz >= count); 228 | 229 | /* Apply to all previous hosts. */ 230 | 231 | if (waittime) 232 | for (j = cfg->urlsz - 1, i = 0; i < count; i++, j--) 233 | cfg->urls[j].waittime = waittime; 234 | if (timeout) 235 | for (j = cfg->urlsz - 1, i = 0; i < count; i++, j--) 236 | cfg->urls[j].timeout = timeout; 237 | 238 | return 1; 239 | } 240 | 241 | /* 242 | * Parse host related bits. 243 | * Returns 0 on failure, <0 to stop parsing the current block (not an 244 | * error), or >0 if the parse should continue. 245 | */ 246 | static int 247 | parse_layout_host_bits(struct parse *p, unsigned int *val) 248 | { 249 | 250 | if (tok_eq_adv(p, "record")) 251 | *val |= HOST_RECORD; 252 | else if (tok_eq_adv(p, "slant_version")) 253 | *val |= HOST_SLANT_VERSION; 254 | else if (tok_eq_adv(p, "uptime")) 255 | *val |= HOST_UPTIME; 256 | else if (tok_eq_adv(p, "clock_drift")) 257 | *val |= HOST_CLOCK_DRIFT; 258 | else if (tok_eq_adv(p, "machine")) 259 | *val |= HOST_MACHINE; 260 | else if (tok_eq_adv(p, "osversion")) 261 | *val |= HOST_OSVERSION; 262 | else if (tok_eq_adv(p, "osrelease")) 263 | *val |= HOST_OSRELEASE; 264 | else if (tok_eq_adv(p, "osname")) 265 | *val |= HOST_OSSYSNAME; 266 | else if (tok_eq(p, ";")) 267 | return -1; 268 | else if (tok_eq(p, "}")) 269 | return -1; 270 | else 271 | return tok_unknown(p); 272 | 273 | return 1; 274 | } 275 | 276 | /* 277 | * Parse rate-data related bits. 278 | * Returns 0 on failure, <0 to stop parsing the current block (not an 279 | * error), or >0 if the parse should continue. 280 | */ 281 | static int 282 | parse_layout_rates(struct parse *p, unsigned int *val) 283 | { 284 | 285 | if (tok_eq_adv(p, "qmin")) 286 | *val |= LINE_QMIN; 287 | else if (tok_eq_adv(p, "min")) 288 | *val |= LINE_MIN; 289 | else if (tok_eq_adv(p, "hour")) 290 | *val |= LINE_HOUR; 291 | else if (tok_eq_adv(p, "day")) 292 | *val |= LINE_DAY; 293 | else if (tok_eq_adv(p, "week")) 294 | *val |= LINE_WEEK; 295 | else if (tok_eq_adv(p, "year")) 296 | *val |= LINE_YEAR; 297 | else if (tok_eq(p, ";")) 298 | return -1; 299 | else if (tok_eq(p, "}")) 300 | return -1; 301 | else 302 | return tok_unknown(p); 303 | 304 | return 1; 305 | } 306 | 307 | /* 308 | * Parse percent-data related bits. 309 | * Returns 0 on failure, <0 to stop parsing the current block (not an 310 | * error), or >0 if the parse should continue. 311 | */ 312 | static int 313 | parse_layout_pcts(struct parse *p, unsigned int *val) 314 | { 315 | 316 | if (tok_eq_adv(p, "qmin_bars")) 317 | *val |= LINE_QMIN_BARS; 318 | else if (tok_eq_adv(p, "min_bars")) 319 | *val |= LINE_MIN_BARS; 320 | else if (tok_eq_adv(p, "hour_bars")) 321 | *val |= LINE_HOUR_BARS; 322 | else if (tok_eq_adv(p, "day_bars")) 323 | *val |= LINE_DAY_BARS; 324 | else if (tok_eq_adv(p, "week_bars")) 325 | *val |= LINE_WEEK_BARS; 326 | else if (tok_eq_adv(p, "year_bars")) 327 | *val |= LINE_YEAR_BARS; 328 | else if (tok_eq_adv(p, "qmin")) 329 | *val |= LINE_QMIN; 330 | else if (tok_eq_adv(p, "min")) 331 | *val |= LINE_MIN; 332 | else if (tok_eq_adv(p, "hour")) 333 | *val |= LINE_HOUR; 334 | else if (tok_eq_adv(p, "day")) 335 | *val |= LINE_DAY; 336 | else if (tok_eq_adv(p, "week")) 337 | *val |= LINE_WEEK; 338 | else if (tok_eq_adv(p, "year")) 339 | *val |= LINE_YEAR; 340 | else if (tok_eq(p, ";")) 341 | return -1; 342 | else if (tok_eq(p, "}")) 343 | return -1; 344 | else 345 | return tok_unknown(p); 346 | 347 | return 1; 348 | } 349 | 350 | static int 351 | parse_layout_host(struct parse *p, struct config *cfg) 352 | { 353 | void *pp; 354 | struct drawbox *b; 355 | int rc, in_line; 356 | unsigned int *line; 357 | 358 | if ( ! tok_expect_adv(p, "{")) 359 | return 0; 360 | if (tok_eq(p, "}")) 361 | return 1; 362 | 363 | while (p->pos < p->toksz) { 364 | pp = reallocarray(cfg->draw->box, 365 | cfg->draw->boxsz + 1, 366 | sizeof(struct drawbox)); 367 | if (NULL == pp) { 368 | warn(NULL); 369 | return 0; 370 | } 371 | 372 | cfg->draw->box = pp; 373 | b = &cfg->draw->box[cfg->draw->boxsz++]; 374 | memset(b, 0, sizeof(struct drawbox)); 375 | 376 | /* 377 | * What kind of category are we? 378 | * That is to say: what will we be showing in this box? 379 | */ 380 | 381 | if (tok_eq_adv(p, "cpu")) 382 | b->cat = DRAWCAT_CPU; 383 | else if (tok_eq_adv(p, "mem")) 384 | b->cat = DRAWCAT_MEM; 385 | else if (tok_eq_adv(p, "net")) 386 | b->cat = DRAWCAT_NET; 387 | else if (tok_eq_adv(p, "disc")) 388 | b->cat = DRAWCAT_DISC; 389 | else if (tok_eq_adv(p, "link")) 390 | b->cat = DRAWCAT_LINK; 391 | else if (tok_eq_adv(p, "host")) 392 | b->cat = DRAWCAT_HOST; 393 | else if (tok_eq_adv(p, "nprocs")) 394 | b->cat = DRAWCAT_PROCS; 395 | else if (tok_eq_adv(p, "rprocs")) 396 | b->cat = DRAWCAT_RPROCS; 397 | else if (tok_eq_adv(p, "nfiles")) 398 | b->cat = DRAWCAT_FILES; 399 | else 400 | return tok_unknown(p); 401 | 402 | /* 403 | * We can handle multiple lines, of course. 404 | * If prefixed with "line1" or "line2", that refers to 405 | * the given line. 406 | * No prefix is short-hand for the first line. 407 | */ 408 | againline: 409 | if (tok_eq_adv(p, "line1")) { 410 | in_line = 1; 411 | line = &b->lines[0].line; 412 | if (cfg->draw->maxline < 1) 413 | cfg->draw->maxline = 1; 414 | } else if (tok_eq_adv(p, "line2")) { 415 | in_line = 1; 416 | line = &b->lines[1].line; 417 | if (cfg->draw->maxline < 2) 418 | cfg->draw->maxline = 2; 419 | } else if (tok_eq_adv(p, "line3")) { 420 | in_line = 1; 421 | line = &b->lines[2].line; 422 | if (cfg->draw->maxline < 3) 423 | cfg->draw->maxline = 3; 424 | } else if (tok_eq_adv(p, "line4")) { 425 | in_line = 1; 426 | line = &b->lines[3].line; 427 | if (cfg->draw->maxline < 4) 428 | cfg->draw->maxline = 4; 429 | } else if (tok_eq_adv(p, "line5")) { 430 | in_line = 1; 431 | line = &b->lines[4].line; 432 | if (cfg->draw->maxline < 5) 433 | cfg->draw->maxline = 5; 434 | } else if (tok_eq_adv(p, "line6")) { 435 | in_line = 1; 436 | line = &b->lines[5].line; 437 | if (cfg->draw->maxline < 6) 438 | cfg->draw->maxline = 6; 439 | } else { 440 | in_line = 0; 441 | line = &b->lines[0].line; 442 | if (cfg->draw->maxline < 1) 443 | cfg->draw->maxline = 1; 444 | } 445 | 446 | /* If multiple times, clear. */ 447 | 448 | *line = 0; 449 | 450 | if (in_line && ! tok_expect_adv(p, "{")) 451 | return 0; 452 | 453 | /* Now the information within each box. */ 454 | 455 | switch (b->cat) { 456 | case DRAWCAT_CPU: 457 | case DRAWCAT_FILES: 458 | case DRAWCAT_MEM: 459 | case DRAWCAT_PROCS: 460 | case DRAWCAT_RPROCS: 461 | while (p->pos < p->toksz) { 462 | rc = parse_layout_pcts(p, line); 463 | if (rc < 0) 464 | break; 465 | else if (0 == rc) 466 | return rc; 467 | } 468 | break; 469 | case DRAWCAT_DISC: 470 | case DRAWCAT_NET: 471 | while (p->pos < p->toksz) { 472 | rc = parse_layout_rates(p, line); 473 | if (rc < 0) 474 | break; 475 | else if (0 == rc) 476 | return rc; 477 | } 478 | break; 479 | case DRAWCAT_LINK: 480 | while (p->pos < p->toksz) 481 | if (tok_eq_adv(p, "ip")) 482 | *line |= LINK_IP; 483 | else if (tok_eq_adv(p, "state")) 484 | *line |= LINK_STATE; 485 | else if (tok_eq_adv(p, "access")) 486 | *line |= LINK_ACCESS; 487 | else if (tok_eq(p, ";")) 488 | break; 489 | else if (tok_eq(p, "}")) 490 | break; 491 | else 492 | return tok_unknown(p); 493 | break; 494 | case DRAWCAT_HOST: 495 | while (p->pos < p->toksz) { 496 | rc = parse_layout_host_bits(p, line); 497 | if (rc < 0) 498 | break; 499 | else if (0 == rc) 500 | return rc; 501 | } 502 | break; 503 | } 504 | 505 | if (0 == *line) { 506 | warnx("%s: empty column arguments", p->fn); 507 | return 0; 508 | } 509 | 510 | if (in_line && ! tok_expect_adv(p, "}")) 511 | return 0; 512 | 513 | /* 514 | * If we were in-line (e.g., "line1 { ... }"), then we 515 | * might be followed by further lines. 516 | * Check to see if we're at the end of the box. 517 | */ 518 | 519 | if (in_line) 520 | if ( ! tok_eq(p, ";") && ! tok_eq(p, "}")) 521 | goto againline; 522 | 523 | /* 524 | * If we're at a close-brace, then we're finished with 525 | * all information for this host. 526 | * Otherwise, if we're at a semicolon, then we're going 527 | * to add another box. 528 | */ 529 | 530 | if (tok_eq(p, "}")) 531 | break; 532 | if ( ! tok_expect_adv(p, ";")) 533 | return 0; 534 | if (tok_eq(p, "}")) 535 | break; 536 | } 537 | 538 | if ( ! tok_expect_adv(p, "}")) 539 | return 0; 540 | 541 | return 1; 542 | } 543 | 544 | /* 545 | * Parse a layout statement. 546 | * Return zero on success, non-zero otherwise. 547 | * Fills in the cfg->draw, possibly on failure. 548 | */ 549 | static int 550 | parse_layout(struct parse *p, struct config *cfg) 551 | { 552 | const char *er; 553 | 554 | if ( ! tok_expect_adv(p, "{")) 555 | return 0; 556 | if (tok_eq_adv(p, "}")) 557 | return 1; 558 | 559 | if (NULL != cfg->draw) { 560 | warnx("%s: layout already specified", p->fn); 561 | return 0; 562 | } 563 | if (NULL == (cfg->draw = calloc(1, sizeof(struct draw)))) { 564 | warn(NULL); 565 | return 0; 566 | } 567 | 568 | while (p->pos < p->toksz) { 569 | if (tok_eq_adv(p, "header")) { 570 | cfg->draw->header = 1; 571 | } else if (tok_eq_adv(p, "errlog")) { 572 | cfg->draw->errlog = strtonum 573 | (p->toks[p->pos], 0, INT_MAX, &er); 574 | if (NULL != er) { 575 | warnx("%s: bad layout errlog: " 576 | "%s", p->fn, er); 577 | return 0; 578 | } 579 | p->pos++; 580 | } else if (tok_eq_adv(p, "host")) { 581 | if ( ! parse_layout_host(p, cfg)) 582 | return 0; 583 | } else 584 | return tok_unknown(p); 585 | 586 | if (tok_eq(p, "}")) 587 | break; 588 | if ( ! tok_expect_adv(p, ";")) 589 | return 0; 590 | if (tok_eq(p, "}")) 591 | break; 592 | } 593 | 594 | if ( ! tok_expect_adv(p, "}")) 595 | return 0; 596 | return tok_expect_adv(p, ";"); 597 | } 598 | 599 | /* 600 | * "servers" s1 [s2...] ["{" args] ";" 601 | */ 602 | static int 603 | parse_servers(struct parse *p, struct config *cfg) 604 | { 605 | void *pp; 606 | size_t count = 0; 607 | 608 | while (p->pos < p->toksz) { 609 | if (tok_eq(p, ";") || tok_eq(p, "{")) 610 | break; 611 | pp = reallocarray 612 | (cfg->urls, cfg->urlsz + 1, 613 | sizeof(struct nconfig)); 614 | if (NULL == pp) { 615 | warn(NULL); 616 | return 0; 617 | } 618 | cfg->urls = pp; 619 | memset(&cfg->urls[cfg->urlsz], 620 | 0, sizeof(struct nconfig)); 621 | cfg->urlsz++; 622 | cfg->urls[cfg->urlsz - 1].url = 623 | strdup(p->toks[p->pos]); 624 | if (NULL == cfg->urls[cfg->urlsz - 1].url) { 625 | warn(NULL); 626 | return 0; 627 | } 628 | p->pos++; 629 | count++; 630 | } 631 | 632 | if (0 == count) { 633 | warnx("%s: no servers in statement", p->fn); 634 | return 0; 635 | } else if ( ! tok_nadv(p)) 636 | return 0; 637 | 638 | /* Now the arguments. */ 639 | 640 | if (tok_eq_adv(p, "{")) { 641 | if ( ! parse_server_args(p, cfg, count)) 642 | return 0; 643 | if ( ! tok_expect(p, ";")) 644 | return 0; 645 | } 646 | 647 | p->pos++; 648 | return 1; 649 | } 650 | 651 | static int 652 | config_cmdline(struct config *cfg, int argc, char *argv[]) 653 | { 654 | size_t i; 655 | 656 | if (0 == argc) 657 | return 1; 658 | 659 | cfg->urlsz = argc; 660 | cfg->urls = calloc(cfg->urlsz, sizeof(struct nconfig)); 661 | 662 | if (NULL == cfg->urls) { 663 | warn(NULL); 664 | return 0; 665 | } 666 | 667 | for (i = 0; i < cfg->urlsz; i++) { 668 | cfg->urls[i].url = strdup(argv[i]); 669 | if (NULL == cfg->urls[i].url) { 670 | warn(NULL); 671 | return 0; 672 | } 673 | } 674 | 675 | return 1; 676 | } 677 | 678 | int 679 | config_parse(const char *fn, struct config *cfg, 680 | int argc, char *argv[]) 681 | { 682 | int fd; 683 | void *map, *pp; 684 | char *buf, *bufsv, *cp; 685 | char **toks = NULL; 686 | size_t mapsz, i; 687 | struct stat st; 688 | struct parse p; 689 | 690 | memset(cfg, 0, sizeof(struct config)); 691 | memset(&p, 0, sizeof(struct parse)); 692 | 693 | /* Set some defaults. */ 694 | 695 | cfg->waittime = 60; 696 | cfg->timeout = 60; 697 | 698 | /* Open file, map it, create a NUL-terminated string. */ 699 | 700 | if (-1 == (fd = open(fn, O_RDONLY, 0))) { 701 | if (ENOENT == errno) 702 | return config_cmdline(cfg, argc, argv); 703 | warn("%s", fn); 704 | return 0; 705 | } else if (-1 == fstat(fd, &st)) { 706 | warn("%s", fn); 707 | close(fd); 708 | return 0; 709 | } 710 | 711 | mapsz = (size_t)st.st_size; 712 | if (NULL == (buf = bufsv = malloc(mapsz + 1))) { 713 | warn(NULL); 714 | close(fd); 715 | return 0; 716 | } 717 | map = mmap(NULL, mapsz, PROT_READ, MAP_SHARED, fd, 0); 718 | if (MAP_FAILED == map) { 719 | warn("%s", fn); 720 | free(buf); 721 | close(fd); 722 | return 0; 723 | } 724 | 725 | memcpy(buf, map, mapsz); 726 | buf[mapsz] = '\0'; 727 | munmap(map, mapsz); 728 | close(fd); 729 | 730 | /* 731 | * Step through all space-separated tokens. 732 | * TODO: make this into a proper parsing sequence at some point, 733 | * but for now this will do. 734 | */ 735 | 736 | while (NULL != (cp = strsep(&buf, " \t\r\n"))) { 737 | if ('\0' == *cp) 738 | continue; 739 | pp = reallocarray 740 | (toks, p.toksz + 1, 741 | sizeof(char *)); 742 | if (NULL == pp) { 743 | free(bufsv); 744 | free(toks); 745 | warn(NULL); 746 | return 0; 747 | } 748 | toks = pp; 749 | toks[p.toksz++] = cp; 750 | } 751 | 752 | p.toks = (const char **)toks; 753 | p.fn = fn; 754 | 755 | /* Recursive descent parse top-level driver. */ 756 | 757 | while (p.pos < p.toksz) 758 | if (tok_eq_adv(&p, "servers")) { 759 | if ( ! parse_servers(&p, cfg)) 760 | break; 761 | } else if (tok_eq_adv(&p, "layout")) { 762 | if ( ! parse_layout(&p, cfg)) 763 | break; 764 | } else if (tok_eq_adv(&p, "waittime")) { 765 | if ( ! parse_waittime(&p, cfg)) 766 | break; 767 | } else if (tok_eq_adv(&p, "timeout")) { 768 | if ( ! parse_timeout(&p, cfg)) 769 | break; 770 | } else { 771 | tok_unknown(&p); 772 | break; 773 | } 774 | 775 | free(toks); 776 | free(bufsv); 777 | if (p.pos != p.toksz) 778 | return 0; 779 | 780 | if (0 == argc) 781 | return 1; 782 | 783 | /* Use only our command line entries. */ 784 | 785 | for (i = 0; i < cfg->urlsz; i++) 786 | free(cfg->urls[i].url); 787 | 788 | free(cfg->urls); 789 | cfg->urls = NULL; 790 | cfg->urlsz = 0; 791 | 792 | return config_cmdline(cfg, argc, argv); 793 | } 794 | 795 | void 796 | config_free(struct config *cfg) 797 | { 798 | size_t i; 799 | 800 | if (NULL == cfg) 801 | return; 802 | 803 | for (i = 0; i < cfg->urlsz; i++) 804 | free(cfg->urls[i].url); 805 | if (NULL != cfg->draw) 806 | free(cfg->draw->box); 807 | 808 | free(cfg->draw); 809 | free(cfg->urls); 810 | } 811 | -------------------------------------------------------------------------------- /slant-collectd-openbsd.c: -------------------------------------------------------------------------------- 1 | #ifdef __OpenBSD__ 2 | /* $Id$ */ 3 | /* 4 | * A lot of this file is a restatement of OpenBSD's top(1) machine.c. 5 | * Its copyright and license file is retained below. 6 | */ 7 | /*- 8 | * Copyright (c) 1994 Thorsten Lockert 9 | * Copyright (c) 2018 Thorsten Lockert 10 | * All rights reserved. 11 | * 12 | * Redistribution and use in source and binary forms, with or without 13 | * modification, are permitted provided that the following conditions 14 | * are met: 15 | * 1. Redistributions of source code must retain the above copyright 16 | * notice, this list of conditions and the following disclaimer. 17 | * 2. Redistributions in binary form must reproduce the above copyright 18 | * notice, this list of conditions and the following disclaimer in the 19 | * documentation and/or other materials provided with the distribution. 20 | * 3. The name of the author may not be used to endorse or promote products 21 | * derived from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 24 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 25 | * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 26 | * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 27 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 28 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 29 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 31 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 32 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | /* 35 | * Other parts are from OpenBSD's systat(1) if.c. 36 | * Its copyright and license file is retained below. 37 | */ 38 | /* 39 | * Copyright (c) 2004 Markus Friedl 40 | * Copyright (c) 2018 Kristaps Dzonsons 41 | * 42 | * Permission to use, copy, modify, and distribute this software for any 43 | * purpose with or without fee is hereby granted, provided that the above 44 | * copyright notice and this permission notice appear in all copies. 45 | * 46 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 47 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 48 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 49 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 50 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 51 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 52 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 53 | */ 54 | #include "config.h" 55 | 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | 75 | #include "slant-collectd.h" 76 | 77 | struct ifcount { 78 | u_int64_t ifc_ib; /* input bytes */ 79 | u_int64_t ifc_ip; /* input packets */ 80 | u_int64_t ifc_ie; /* input errors */ 81 | u_int64_t ifc_ob; /* output bytes */ 82 | u_int64_t ifc_op; /* output packets */ 83 | u_int64_t ifc_oe; /* output errors */ 84 | u_int64_t ifc_co; /* collisions */ 85 | int ifc_flags; /* up / down */ 86 | int ifc_state; /* link state */ 87 | }; 88 | 89 | struct ifstat { 90 | struct ifcount ifs_cur; 91 | struct ifcount ifs_old; 92 | struct ifcount ifs_now; 93 | char ifs_flag; 94 | }; 95 | 96 | /* 97 | * Define pagetok in terms of pageshift. 98 | */ 99 | #define PAGETOK(size, pageshift) ((size) << (pageshift)) 100 | 101 | struct sysinfo { 102 | size_t sample; /* sample number */ 103 | int pageshift; /* used for memory pages */ 104 | double mem_avg; /* average memory */ 105 | double nproc_pct; /* nprocs percent */ 106 | double nfile_pct; /* nfiles percent */ 107 | int64_t *cpu_states; /* used for cpu compute */ 108 | double cpu_avg; /* average cpu */ 109 | int64_t **cp_time; /* used for cpu compute */ 110 | int64_t **cp_old; /* used for cpu compute */ 111 | int64_t **cp_diff; /* used for cpu compute */ 112 | size_t ncpu; /* number cpus */ 113 | double rproc_pct; /* pct command (by name) found */ 114 | struct ifstat *ifstats; /* used for inet compute */ 115 | size_t ifstatsz; /* used for inet compute */ 116 | struct ifcount ifsum; /* average inet */ 117 | u_int64_t disc_rbytes; /* last disc total read */ 118 | u_int64_t disc_wbytes; /* last disc total write */ 119 | int64_t disc_ravg; /* average reads/sec */ 120 | int64_t disc_wavg; /* average reads/sec */ 121 | time_t boottime; /* time booted */ 122 | }; 123 | 124 | /* 125 | * Get the number of online (working) CPUs---that is, those that will 126 | * return non-zero working time. 127 | * Return zero on failure, non-zero on success. 128 | * Fills in "p" on success. 129 | */ 130 | static int 131 | getonlinecpu(size_t *p) 132 | { 133 | int mib[] = { CTL_HW, HW_NCPUONLINE }; 134 | int numcpu; 135 | size_t size = sizeof(numcpu); 136 | 137 | if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), 138 | &numcpu, &size, NULL, 0) == -1) { 139 | warn("sysctl: CTL_HW, HW_NCPUONLINE"); 140 | return 0; 141 | } 142 | 143 | *p = numcpu; 144 | return 1; 145 | } 146 | 147 | /* 148 | * Get the number of configured CPUs. 149 | * This might be less than the number of working CPUs. 150 | * Return zero on failure, non-zero on success. 151 | * Fills in "p" on success. 152 | */ 153 | static int 154 | getncpu(size_t *p) 155 | { 156 | int mib[] = { CTL_HW, HW_NCPU }; 157 | int numcpu; 158 | size_t size = sizeof(numcpu); 159 | 160 | if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), 161 | &numcpu, &size, NULL, 0) == -1) { 162 | warn("sysctl: CTL_HW, HW_NCPU"); 163 | return 0; 164 | } 165 | 166 | *p = numcpu; 167 | return 1; 168 | } 169 | 170 | static void 171 | percentages(int cnt, int64_t *out, 172 | int64_t *new, int64_t *old, int64_t *diffs) 173 | { 174 | int64_t change, tot, *dp, half_total; 175 | int i; 176 | 177 | /* initialization */ 178 | 179 | tot = 0; 180 | dp = diffs; 181 | 182 | /* calculate changes for each state and the overall change */ 183 | 184 | for (i = 0; i < cnt; i++) { 185 | if ((change = *new - *old) < 0) { 186 | /* this only happens when the counter wraps */ 187 | change = INT64_MAX - *old + *new; 188 | } 189 | tot += (*dp++ = change); 190 | *old++ = *new++; 191 | } 192 | 193 | /* avoid divide by zero potential */ 194 | 195 | if (tot == 0) 196 | tot = 1; 197 | 198 | /* calculate percentages based on overall change, rounding up */ 199 | 200 | half_total = tot / 2l; 201 | for (i = 0; i < cnt; i++) 202 | *out++ = ((*diffs++ * 1000 + half_total) / tot); 203 | } 204 | 205 | void 206 | sysinfo_free(struct sysinfo *p) 207 | { 208 | size_t i; 209 | 210 | if (NULL == p) 211 | return; 212 | 213 | for (i = 0; i < p->ncpu; i++) { 214 | free(p->cp_time[i]); 215 | free(p->cp_old[i]); 216 | free(p->cp_diff[i]); 217 | } 218 | 219 | free(p->cp_time); 220 | free(p->cp_old); 221 | free(p->cp_diff); 222 | free(p->cpu_states); 223 | free(p->ifstats); 224 | free(p); 225 | } 226 | 227 | static int 228 | sysinfo_init_boottime(struct sysinfo *p) 229 | { 230 | int bt_mib[] = { CTL_KERN, KERN_BOOTTIME }; 231 | struct timeval tv; 232 | size_t size; 233 | 234 | size = sizeof(struct timeval); 235 | if (sysctl(bt_mib, 2, &tv, &size, NULL, 0) < 0) { 236 | warn("sysctl: CTL_KERN, KERN_BOOTTIME"); 237 | return 0; 238 | } 239 | 240 | p->boottime = tv.tv_sec; 241 | return 1; 242 | } 243 | 244 | struct sysinfo * 245 | sysinfo_alloc(void) 246 | { 247 | struct sysinfo *p; 248 | size_t i; 249 | int pagesize; 250 | 251 | p = calloc(1, sizeof(struct sysinfo)); 252 | if (NULL == p) { 253 | warn(NULL); 254 | return NULL; 255 | } 256 | 257 | if ( ! getncpu(&p->ncpu)) { 258 | warn(NULL); 259 | sysinfo_free(p); 260 | return NULL; 261 | } 262 | 263 | assert(p->ncpu > 0); 264 | 265 | p->cpu_states = calloc 266 | (p->ncpu, CPUSTATES * sizeof(int64_t)); 267 | p->cp_time = calloc(p->ncpu, sizeof(int64_t *)); 268 | p->cp_old = calloc(p->ncpu, sizeof(int64_t *)); 269 | p->cp_diff = calloc(p->ncpu, sizeof(int64_t *)); 270 | 271 | if (NULL == p->cpu_states || 272 | NULL == p->cp_time || 273 | NULL == p->cp_old || 274 | NULL == p->cp_diff) { 275 | warn(NULL); 276 | sysinfo_free(p); 277 | return NULL; 278 | } 279 | 280 | for (i = 0; i < p->ncpu; i++) { 281 | p->cp_time[i] = calloc(CPUSTATES, sizeof(int64_t)); 282 | p->cp_old[i] = calloc(CPUSTATES, sizeof(int64_t)); 283 | p->cp_diff[i] = calloc(CPUSTATES, sizeof(int64_t)); 284 | if (NULL == p->cp_time[i] || 285 | NULL == p->cp_old[i] || 286 | NULL == p->cp_diff[i]) { 287 | warn(NULL); 288 | sysinfo_free(p); 289 | return NULL; 290 | } 291 | } 292 | 293 | /* 294 | * get the page size with "getpagesize" and calculate pageshift 295 | * from it 296 | */ 297 | 298 | pagesize = getpagesize(); 299 | p->pageshift = 0; 300 | while (pagesize > 1) { 301 | p->pageshift++; 302 | pagesize >>= 1; 303 | } 304 | 305 | /* we only need the amount of log(2)1024 for our conversion */ 306 | 307 | p->pageshift -= 10; 308 | 309 | if ( ! sysinfo_init_boottime(p)) { 310 | sysinfo_free(p); 311 | return NULL; 312 | } 313 | 314 | return p; 315 | } 316 | 317 | static int 318 | sysinfo_update_mem(struct sysinfo *p) 319 | { 320 | int uvmexp_mib[] = { CTL_VM, VM_UVMEXP }; 321 | struct uvmexp uvmexp; 322 | size_t size; 323 | 324 | size = sizeof(uvmexp); 325 | if (sysctl(uvmexp_mib, 2, &uvmexp, &size, NULL, 0) < 0) { 326 | warn("sysctl: CTL_VM, VM_UVMEXP"); 327 | return 0; 328 | } 329 | 330 | p->mem_avg = 100.0 * 331 | PAGETOK(uvmexp.active, p->pageshift) / 332 | (double)PAGETOK(uvmexp.npages, p->pageshift); 333 | return 1; 334 | } 335 | 336 | static int 337 | sysinfo_update_nfiles(const struct syscfg *cfg, struct sysinfo *p) 338 | { 339 | size_t size; 340 | int maxfile, nfiles; 341 | int cp_nfile_mib[] = { CTL_KERN, KERN_NFILES }, 342 | cp_maxfile_mib[] = { CTL_KERN, KERN_MAXFILES }; 343 | 344 | size = sizeof(int); 345 | if (sysctl(cp_maxfile_mib, 2, &maxfile, &size, NULL, 0) < 0) { 346 | warn("sysctl: CTL_KERN, KERN_MAXFILES"); 347 | return 0; 348 | } else if (0 == maxfile) { 349 | warnx("sysctl: CTL_KERN, KERN_MAXFILES returns 0"); 350 | return 0; 351 | } 352 | 353 | size = sizeof(int); 354 | if (sysctl(cp_nfile_mib, 2, 355 | &nfiles, &size, NULL, 0) < 0) { 356 | warn("sysctl: CTL_KERN, KERN_NFILES"); 357 | return 0; 358 | } 359 | p->nfile_pct = 100.0 * nfiles / (double)maxfile; 360 | return 1; 361 | } 362 | 363 | static int 364 | sysinfo_update_nprocs(const struct syscfg *cfg, struct sysinfo *p) 365 | { 366 | size_t i, j, size, len, rprocs = 0; 367 | int maxproc, nprocs; 368 | int cp_nproc_mib[] = { CTL_KERN, KERN_NPROCS }, 369 | cp_maxproc_mib[] = { CTL_KERN, KERN_MAXPROC }, 370 | cp_procs_mib[] = { CTL_KERN, KERN_PROC, 371 | 0, 0, sizeof(struct kinfo_proc), 0}; 372 | struct kinfo_proc *pb = NULL; 373 | 374 | size = sizeof(int); 375 | if (sysctl(cp_maxproc_mib, 2, &maxproc, &size, NULL, 0) < 0) { 376 | warn("sysctl: CTL_KERN, KERN_MAXPROC"); 377 | return 0; 378 | } else if (0 == maxproc) { 379 | warnx("sysctl: CTL_KERN, KERN_MAXPROC returns 0"); 380 | return 0; 381 | } 382 | 383 | /* 384 | * If we're only going to look at the number of processes, then 385 | * there's no need for us to look at the kinfo_proc: we always 386 | * have 100% running (of... none). 387 | * So short-circuit here. 388 | */ 389 | 390 | if (0 == cfg->cmdsz) { 391 | size = sizeof(int); 392 | if (sysctl(cp_nproc_mib, 2, 393 | &nprocs, &size, NULL, 0) < 0) { 394 | warn("sysctl: CTL_KERN, KERN_NPROCS"); 395 | return 0; 396 | } 397 | p->nproc_pct = 100.0 * nprocs / (double)maxproc; 398 | p->rproc_pct = 100.0; 399 | return 1; 400 | } 401 | retry: 402 | free(pb); 403 | size = sizeof(int); 404 | if (sysctl(cp_procs_mib, 6, NULL, &size, NULL, 0) < 0) { 405 | warn("sysctl: CTL_KERN, KERN_PROC"); 406 | return 0; 407 | } 408 | 409 | /* Add extra slop for new interim processes. */ 410 | 411 | size = 5 * size / 4; 412 | if (NULL == (pb = malloc(size))) { 413 | warn(NULL); 414 | return 0; 415 | } 416 | 417 | cp_procs_mib[5] = (int)(size / sizeof(struct kinfo_proc)); 418 | if (sysctl(cp_procs_mib, 6, pb, &size, NULL, 0) < 0) { 419 | if (errno == ENOMEM) 420 | goto retry; 421 | warn("sysctl: CTL_KERN, KERN_PROC"); 422 | free(pb); 423 | return 0; 424 | } 425 | 426 | len = size / sizeof(struct kinfo_proc); 427 | for (j = 0; j < cfg->cmdsz; j++) { 428 | for (i = 0; i < len; i++) { 429 | if (0 == strcmp(pb[i].p_comm, cfg->cmds[j])) 430 | break; 431 | } 432 | if (i < len) 433 | rprocs++; 434 | } 435 | 436 | free(pb); 437 | p->nproc_pct = 100.0 * len / (double)maxproc; 438 | p->rproc_pct = 100.0 * rprocs / (double)cfg->cmdsz; 439 | return 1; 440 | } 441 | 442 | static int 443 | sysinfo_update_cpu(struct sysinfo *p) 444 | { 445 | size_t i, pos, size, online = 0; 446 | long cp_time_tmp[CPUSTATES]; 447 | int64_t val; 448 | double sum = 0.0; 449 | int64_t *tmpstate; 450 | 451 | if ( ! getonlinecpu(&online)) 452 | return 0; 453 | assert(online > 0); 454 | 455 | /* 456 | * If the CPU is offline (like with disabled hyperthreads), the 457 | * CPU will return zero percent usage. 458 | * We use getonlinecpu() to make sure that when we divide our 459 | * accumulated percentage, it works out. 460 | * We can also use KERN_CPUSTATS. 461 | */ 462 | 463 | if (p->ncpu > 1) { 464 | int cp_time_mib[] = 465 | { CTL_KERN, KERN_CPTIME2, /*fillme*/0 }; 466 | size = CPUSTATES * sizeof(int64_t); 467 | 468 | for (i = 0; i < p->ncpu; i++) { 469 | cp_time_mib[2] = i; 470 | tmpstate = p->cpu_states + (CPUSTATES * i); 471 | if (sysctl(cp_time_mib, 3, 472 | p->cp_time[i], &size, NULL, 0) < 0) { 473 | warn("sysctl: CTL_KERN, KERN_CPTIME2"); 474 | return 0; 475 | } 476 | percentages(CPUSTATES, tmpstate, 477 | p->cp_time[i], p->cp_old[i], 478 | p->cp_diff[i]); 479 | } 480 | } else { 481 | int cp_time_mib[] = { CTL_KERN, KERN_CPTIME }; 482 | size = sizeof(cp_time_tmp); 483 | 484 | if (sysctl(cp_time_mib, 2, 485 | cp_time_tmp, &size, NULL, 0) < 0) { 486 | warn("sysctl: CTL_KERN, KERN_CPTIME"); 487 | return 0; 488 | } 489 | for (i = 0; i < CPUSTATES; i++) 490 | p->cp_time[0][i] = cp_time_tmp[i]; 491 | percentages(CPUSTATES, p->cpu_states, p->cp_time[0], 492 | p->cp_old[0], p->cp_diff[0]); 493 | } 494 | 495 | /* Update our averages. */ 496 | 497 | for (i = 0; i < p->ncpu; i++) { 498 | pos = i * CPUSTATES + CP_IDLE; 499 | val = 1000 - p->cpu_states[pos]; 500 | if (val > 1000) { 501 | warnx("CPU state out of bound: %" PRId64, val); 502 | val = 1000; 503 | } else if (val < 0) { 504 | warnx("CPU state out of bound: %" PRId64, val); 505 | val = 0; 506 | } 507 | sum += val / 10.; 508 | } 509 | 510 | p->cpu_avg = sum / online; 511 | return 1; 512 | } 513 | 514 | #define UPDATE(x, y, up) \ 515 | do { \ 516 | ifs->ifs_now.x = ifm.y; \ 517 | ifs->ifs_cur.x = ifs->ifs_now.x - ifs->ifs_old.x; \ 518 | ifs->ifs_old.x = ifs->ifs_now.x; \ 519 | ifs->ifs_cur.x /= 15; \ 520 | if ((up)) \ 521 | p->ifsum.x += ifs->ifs_cur.x; \ 522 | } while(0) 523 | 524 | static int 525 | sysinfo_update_if(struct sysinfo *p) 526 | { 527 | struct ifstat *newstats, *ifs; 528 | struct if_msghdr ifm; 529 | char *buf, *next, *lim; 530 | int mib[6]; 531 | size_t need, up; 532 | 533 | mib[0] = CTL_NET; 534 | mib[1] = PF_ROUTE; 535 | mib[2] = 0; 536 | mib[3] = 0; 537 | mib[4] = NET_RT_IFLIST; 538 | mib[5] = 0; 539 | 540 | if (-1 == sysctl(mib, 6, NULL, &need, NULL, 0)) { 541 | warn("sysctl: CTL_NET, PF_ROUTE, NET_RT_IFLIST"); 542 | return 0; 543 | } else if (NULL == (buf = malloc(need))) { 544 | warn(NULL); 545 | return 0; 546 | } else if (-1 == sysctl(mib, 6, buf, &need, NULL, 0)) { 547 | warn("sysctl: CTL_NET, PF_ROUTE, NET_RT_IFLIST"); 548 | free(buf); 549 | return 0; 550 | } 551 | 552 | memset(&p->ifsum, 0, sizeof(p->ifsum)); 553 | 554 | lim = buf + need; 555 | for (next = buf; next < lim; next += ifm.ifm_msglen) { 556 | memcpy(&ifm, next, sizeof(ifm)); 557 | 558 | if (ifm.ifm_version != RTM_VERSION || 559 | ifm.ifm_type != RTM_IFINFO || 560 | ! (ifm.ifm_addrs & RTA_IFP)) 561 | continue; 562 | 563 | if (ifm.ifm_index >= p->ifstatsz) { 564 | newstats = reallocarray 565 | (p->ifstats, ifm.ifm_index + 4, 566 | sizeof(struct ifstat)); 567 | if (NULL == newstats) { 568 | warn(NULL); 569 | free(buf); 570 | return 0; 571 | } 572 | p->ifstats = newstats; 573 | while (p->ifstatsz < ifm.ifm_index + 4) { 574 | memset(&p->ifstats[p->ifstatsz], 575 | 0, sizeof(*p->ifstats)); 576 | p->ifstatsz++; 577 | } 578 | } 579 | 580 | ifs = &p->ifstats[ifm.ifm_index]; 581 | 582 | /* Only consider non-loopback up addresses. */ 583 | 584 | up = (ifs->ifs_cur.ifc_flags & IFF_UP) && 585 | ! (ifs->ifs_cur.ifc_flags & IFF_LOOPBACK); 586 | 587 | UPDATE(ifc_ip, ifm_data.ifi_ipackets, up); 588 | UPDATE(ifc_ib, ifm_data.ifi_ibytes, up); 589 | UPDATE(ifc_ie, ifm_data.ifi_ierrors, up); 590 | UPDATE(ifc_op, ifm_data.ifi_opackets, up); 591 | UPDATE(ifc_ob, ifm_data.ifi_obytes, up); 592 | UPDATE(ifc_oe, ifm_data.ifi_oerrors, up); 593 | UPDATE(ifc_co, ifm_data.ifi_collisions, up); 594 | 595 | ifs->ifs_cur.ifc_flags = ifm.ifm_flags; 596 | ifs->ifs_cur.ifc_state = ifm.ifm_data.ifi_link_state; 597 | ifs->ifs_flag++; 598 | } 599 | 600 | free(buf); 601 | return 1; 602 | } 603 | 604 | static int 605 | sysinfo_update_disc(const struct syscfg *cfg, struct sysinfo *p) 606 | { 607 | struct diskstats q; 608 | size_t i, need; 609 | int mib[2]; 610 | char *buf, *lim, *next; 611 | u_int64_t rb = 0, wb = 0; 612 | 613 | mib[0] = CTL_HW; 614 | mib[1] = HW_DISKSTATS; 615 | 616 | if (sysctl(mib, 2, NULL, &need, NULL, 0) < 0) { 617 | warn("sysctl: CTL_HW, HW_DISKSTATS"); 618 | return 0; 619 | } else if (NULL == (buf = malloc(need))) { 620 | warn(NULL); 621 | return 0; 622 | } else if (-1 == sysctl(mib, 2, buf, &need, NULL, 0)) { 623 | warn("sysctl: CTL_HW, HW_DISKSTATS"); 624 | free(buf); 625 | return 0; 626 | } 627 | 628 | lim = buf + need; 629 | for (next = buf; next < lim; next += sizeof(q)) { 630 | memcpy(&q, next, sizeof(q)); 631 | for (i = 0; i < cfg->discsz; i++) 632 | if (0 == strcmp(cfg->discs[i], q.ds_name)) 633 | break; 634 | if (cfg->discsz && i == cfg->discsz) 635 | continue; 636 | rb += q.ds_rbytes; 637 | wb += q.ds_wbytes; 638 | } 639 | 640 | if (rb > p->disc_rbytes) { 641 | p->disc_ravg = (rb - p->disc_rbytes) / 15; 642 | p->disc_rbytes = rb; 643 | } else { 644 | p->disc_ravg = 0; 645 | p->disc_rbytes = rb; 646 | } 647 | 648 | if (wb > p->disc_wbytes) { 649 | p->disc_wavg = (wb - p->disc_wbytes) / 15; 650 | p->disc_wbytes = wb; 651 | } else { 652 | p->disc_wavg = 0; 653 | p->disc_wbytes = wb; 654 | } 655 | 656 | free(buf); 657 | return 1; 658 | } 659 | 660 | int 661 | sysinfo_update(const struct syscfg *cfg, struct sysinfo *p) 662 | { 663 | 664 | if ( ! sysinfo_update_nprocs(cfg, p)) 665 | return 0; 666 | if ( ! sysinfo_update_nfiles(cfg, p)) 667 | return 0; 668 | if ( ! sysinfo_update_cpu(p)) 669 | return 0; 670 | if ( ! sysinfo_update_mem(p)) 671 | return 0; 672 | if ( ! sysinfo_update_if(p)) 673 | return 0; 674 | if ( ! sysinfo_update_disc(cfg, p)) 675 | return 0; 676 | 677 | p->sample++; 678 | return 1; 679 | } 680 | 681 | double 682 | sysinfo_get_cpu_avg(const struct sysinfo *p) 683 | { 684 | 685 | return p->cpu_avg; 686 | } 687 | 688 | double 689 | sysinfo_get_mem_avg(const struct sysinfo *p) 690 | { 691 | 692 | return p->mem_avg; 693 | } 694 | 695 | int64_t 696 | sysinfo_get_nettx_avg(const struct sysinfo *p) 697 | { 698 | 699 | if (1 == p->sample) 700 | return 0; 701 | return p->ifsum.ifc_ob; 702 | } 703 | 704 | int64_t 705 | sysinfo_get_netrx_avg(const struct sysinfo *p) 706 | { 707 | 708 | if (1 == p->sample) 709 | return 0; 710 | return p->ifsum.ifc_ib; 711 | } 712 | 713 | int64_t 714 | sysinfo_get_discread_avg(const struct sysinfo *p) 715 | { 716 | 717 | if (1 == p->sample) 718 | return 0; 719 | return p->disc_ravg; 720 | } 721 | 722 | int64_t 723 | sysinfo_get_discwrite_avg(const struct sysinfo *p) 724 | { 725 | 726 | if (1 == p->sample) 727 | return 0; 728 | return p->disc_wavg; 729 | } 730 | 731 | double 732 | sysinfo_get_rprocs(const struct sysinfo *p) 733 | { 734 | 735 | return p->rproc_pct; 736 | } 737 | 738 | double 739 | sysinfo_get_nfiles(const struct sysinfo *p) 740 | { 741 | 742 | return p->nfile_pct; 743 | } 744 | 745 | double 746 | sysinfo_get_nprocs(const struct sysinfo *p) 747 | { 748 | 749 | return p->nproc_pct; 750 | } 751 | 752 | time_t 753 | sysinfo_get_boottime(const struct sysinfo *p) 754 | { 755 | 756 | return p->boottime; 757 | } 758 | #endif 759 | --------------------------------------------------------------------------------