├── LICENSE ├── readme.md ├── screenshots ├── cpu_thresholds.png └── intro.png ├── scripts ├── cpu.sh ├── cpu_collect.sh ├── helpers.sh ├── loadavg.sh ├── mem.sh └── swap.sh └── sysstat.tmux /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2017 Alexey Samoshkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | Tmux sysstat plugin 3 | =================== 4 | Allow to print CPU usage, memory & swap, load average, net I/O metrics in Tmux status bar 5 | 6 | ![intro](/screenshots/intro.png) 7 | 8 | You might checkout [tmux-config](https://github.com/samoshkin/tmux-config) repo to see this plugin in action. 9 | 10 | Features 11 | -------- 12 | - CPU usage 13 | - Memory available/free, used, total (KiB,MiB,GiB), free/used % 14 | - Swap used, free, total, free/used % 15 | - load average for last 1,5,15 minutes 16 | - configurable thresholds (low, medium, stress) with custom colors 17 | - tweak each metric output using templates (e.g, 'used 10% out of 16G') 18 | - configurable size scale (K,M,G) 19 | - OSX, Linux support 20 | - [ ] **TODO:** network I/O metric support 21 | 22 | Tested on: OS X El Capitan 10.11.5, Ubuntu 14 LTS, CentOS 7, FreeBSD 11.1. 23 | 24 | 25 | Installation 26 | ------------ 27 | Best installed through [Tmux Plugin Manager](https://github.com/tmux-plugins/tpm) (TMP). Add following line to your `.tmux.conf` file: 28 | 29 | ``` 30 | set -g @plugin 'samoshkin/tmux-plugin-sysstat' 31 | ``` 32 | 33 | Use `prefix + I` from inside tmux to install all plugins and source them. If you prefer, same effect can be achieved from [command line](https://github.com/tmux-plugins/tpm/blob/master/docs/managing_plugins_via_cmd_line.md): 34 | 35 | ``` 36 | $ ~.tmux/plugins/tpm/bin/install_plugins 37 | ``` 38 | 39 | Basic usage 40 | ----------- 41 | 42 | Once plugged in, tmux `status-left` or `status-right` options can be configured with following placeholders. Each placeholder will be expanded to metric's default output. 43 | 44 | - `#{sysstat_cpu}`, CPU usage - `CPU:40.2%` 45 | - `#{sysstat_mem}`, memory usage - `MEM:73%` 46 | - `#{sysstat_swap}`, swap usage - `SW:66%` 47 | - `#{sysstat_loadavg}`, system load average - `0.25 0.04 0.34` 48 | 49 | For example: 50 | ``` 51 | set -g status-right "#{sysstat_cpu} | #{sysstat_mem} | #{sysstat_swap} | #{sysstat_loadavg} | #[fg=cyan]#(echo $USER)#[default]@#H" 52 | ``` 53 | 54 | Changing default output 55 | ------------------------ 56 | 57 | You can change default output for CPU and memory metrics, if you need more fields to show, or you want to provide custom template. In your `.tmux.conf`: 58 | 59 | For example, to get `Used 4.5G out of 16G` output for memory metric: 60 | 61 | ``` 62 | set -g @sysstat_mem_view_tmpl '#Used [fg=#{mem.color}]#{mem.used}#[default] out of #{mem.total}' 63 | ``` 64 | 65 | If you don't want `CPU:` prefix and don't like colored output for CPU metric: 66 | 67 | ``` 68 | set -g @sysstat_cpu_view_tmpl '#{cpu.pused}' 69 | ``` 70 | 71 | ### Supported fields 72 | 73 | As you can see, each metric can be configured with template, containing fixed text (`CPU:`), color placeholder (`#[fg=#{mem.color}]`) and field placeholder (`#{mem.used}`). This approach gives you the ultimate control over the output for each metric. Following field placeholders are supported: 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
CPU
#{cpu.color}main metric color
#{cpu.pused}CPU usage percentage
86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
Memory
#{mem.color}main metric color
#{mem.free}free/available memory
#{mem.pfree}free memory percentage against total
#{mem.used}used memory
#{mem.pused}used memory percentage against total
#{mem.total}total installed memory
114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 |
Swap
#{swap.color}main swap metric color
#{swap.free}free swap memory
#{swap.pfree}free swap memory percentage against total swap space
#{swap.used}used swap memory
#{swap.pused}used swap memory percentage against total swap space
#{swap.total}total swap space
142 | 143 | ### Change size scale 144 | 145 | free/used/total memory can be shown both in absolute and relative units. When it comes to absolute units, you can choose *size scale factor* to choose between GiB, MiB, KiB. Default is GiB. If you have less than 3-4G memory installed, it makes sense to use MiB. KiB option is less practical, because it yields pretty lengthy output, which does not fit status bar limited estate. 146 | 147 | ``` 148 | set -g @sysstat_mem_size_unit "G" 149 | ``` 150 | 151 | If you choose `G` for size scale, output will have `%.1f` (1 digit after floating point), otherwise size is integer (4.5G, 1024M, 1232345K). 152 | 153 | Thresholds and colored output 154 | --------------- 155 | Each metric output is colored by default. Colors vary depending on metric value. 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 |
ThresholdCPUMemorySwapDefault color
lowx < 30%x < 75%x < 25%green
medium30% < x < 80%75% < x < 90%25% < x < 75%yellow
highx > 80%x > 90%x > 75%red
187 | 188 | You can change thresholds in your `.tmux.conf`: 189 | 190 | ``` 191 | set -g @sysstat_cpu_medium_threshold "75" 192 | set -g @sysstat_cpu_stress_threshold "95" 193 | 194 | set -g @sysstat_mem_medium_threshold "85" 195 | set -g @sysstat_mem_stress_threshold "95" 196 | 197 | set -g @sysstat_swap_medium_threshold "80" 198 | set -g @sysstat_swap_stress_threshold "90" 199 | ``` 200 | 201 | You can change colors for each threshold individually. You can use ANSI basic colors (red, cyan, green) or if your terminal supports 256 colors (and most do nowadays), use `colourXXX` format. 202 | 203 | ``` 204 | set -g @sysstat_cpu_color_low "colour076" 205 | set -g @sysstat_cpu_color_medium "colour220" 206 | set -g @sysstat_cpu_color_stress "colour160" 207 | set -g @sysstat_mem_color_low "green" 208 | set -g @sysstat_mem_color_medium "blue" 209 | set -g @sysstat_mem_color_stress "cyan" 210 | ``` 211 | 212 | `#{(mem|cpu|swap).color}` placeholder in your `@sysstat_(mem|cpu|swap)_view_tmpl` would be replaced by corresponding color, depending on whether metric value falls in particular threshold. 213 | 214 | ### 256 color palette support 215 | 216 | For 256 color palette support, make sure that `tmux` and parent terminal are configured with correct terminal type. See [here](https://unix.stackexchange.com/questions/1045/getting-256-colors-to-work-in-tmux) and [there](https://github.com/tmux/tmux/wiki/FAQ) 217 | 218 | ``` 219 | # ~/.tmux.conf 220 | set -g default-terminal "screen-256color" 221 | ``` 222 | 223 | ``` 224 | # parent terminal 225 | $ echo $TERM 226 | xterm-256color 227 | 228 | # jump into a tmux session 229 | $ tmux new 230 | $ echo $TERM 231 | screen-256color 232 | ``` 233 | 234 | 235 | 236 | ### Multiple colors for each threshold 237 | 238 | You can have up to *3* colors configured for each threshold. To understand why you might need this, let tackle this task. Note, this is rather advanced use case. 239 | 240 | > I want `CPU: #{cpu.pused}` metric output, have green and yellow text colors at "low" and "medium" threshold, and finally, for "high" threshold, I want to use red color, but reverse foreground and background, that is use red for background, and white for text. More over I want "CPU:" text colored apart in red 241 | 242 | Like this: 243 | 244 | ![cpu threshold with custom colors](/screenshots/cpu_thresholds.png) 245 | 246 | You can achieve the result using following configuration: 247 | 248 | ``` 249 | set -g @sysstat_cpu_view_tmpl '#[fg=#{cpu.color3}]CPU:#[default] #[fg=#{cpu.color},bg=#{cpu.color2}]#{cpu.pused}#[default]' 250 | 251 | set -g @sysstat_cpu_color_low "$color_level_ok default default" 252 | set -g @sysstat_cpu_color_medium "$color_level_warn default default" 253 | set -g @sysstat_cpu_color_stress "white,bold $color_level_stress $color_level_stress" 254 | ``` 255 | 256 | Tmux status-interval setting 257 | ----------------------------- 258 | You can configure status refresh interval, increasing or reducing frequency of `tmux-plugin-sysstat` command invocations. 259 | 260 | ``` 261 | set -g status-interval 5 262 | ``` 263 | 264 | It's adviced to set `status-interval` to some reasonable value, like 5-10 seconds. More frequent updates (1 second) are useless, because they distract, and results in extra resource stress spent on metrics calculation itself. 265 | 266 | 267 | 268 | Internals: CPU calculation 269 | -------------------------------------------------- 270 | **NOTE:** Stop here if you want to just use this plugin without making your feet wet. If you're hardcore tmux user and are curious about internals, keep reading 271 | 272 | Internally, we use `iostat` and `top` on OSX, and `vmstat` and `top` on Linux to collect metric value. Neither requires you to install extra packages. These commands are run in sampling mode to report stats every N seconds M times. First sample include average values since the system start. Second one is the average CPU per second for last N seconds (exactly what we need) 273 | 274 | For example: 275 | 276 | ``` 277 | $ iostat -c 2 -w 5 278 | disk0 cpu load average 279 | KB/t tps MB/s us sy id 1m 5m 15m 280 | 44.22 6 0.26 3 2 95 1.74 1.90 2.15 281 | 5.47 8 0.04 4 5 91 1.84 1.92 2.16 << use this row, 2nd sample 282 | ``` 283 | 284 | We align CPU calculation intervals (`-w`) with tmux status bar refresh interval (`status-interval` setting). 285 | 286 | Internals: memory calculation 287 | ---------------------------- 288 | You might ask what we treat as `free` memory and how it's calculated. 289 | 290 | ### OSX 291 | Let's start with OSX. We use `vm_stat` command (not same as `vmstat` on Linux), which reports following data (number of memory pages, not KB): 292 | 293 | ``` 294 | $ vm_stat 295 | Pages free: 37279 296 | Pages active: 1514200 297 | Pages inactive: 1152997 298 | Pages speculative: 6214 299 | Pages throttled: 0 300 | Pages wired down: 1174408 301 | Pages purgeable: 15405 302 | Pages stored in compressor: 1615663 303 | Pages occupied by compressor: 306717 304 | ``` 305 | 306 | Total installed memory formula is: 307 | ``` 308 | Total = free + active + inactive + speculative + occupied by compressor + wired 309 | ``` 310 | 311 | where 312 | - `free`, completely unused memory by the system 313 | - `wired`, critical information stored in RAM by system, kernel and key applications. Never swapped to the hard drive, never replaced with user-level data. 314 | - `active`, information currently in use or very recently used by applications. When this kind of memory is not used for long (or application is closed), it's move to inactive memory. 315 | - `inactive`, like buffers/cached memory in Linux. Memory for applications, which recently exited, retained for faster start-up of same application in future. 316 | 317 | So the question what constitutes `free` and `used` memory. It turns out, that various monitoring and system statistics tools on OSX each calculate it differently. 318 | 319 | - htop: `used = active + wired`, `free` = `total - used` 320 | - top. Used = `used = active + inactive + occupied by compressor + wired`; Free = `free + speculative` Resident set size (RSS) = `active` 321 | - OSX activity Monitor. Used = `app memory + wired + compressor`. Note, it's not clear what is app memory. 322 | 323 | In general, they either treat currently used memory, which can be reclaimed in case of need (cached, inactive, occupied by compressor), as `used` or `free`. 324 | 325 | It makes sense to talk about `available` memory rather than `free` one. Available memory is unused memory + any used memory which can be reclaimed for application needs. 326 | 327 | So, `tmux-plugin-sysstat`, uses following formula: 328 | 329 | ``` 330 | used = active + wired 331 | available/free = free/unused + inactive + speculative + occupied by compressor 332 | ``` 333 | 334 | ### Linux 335 | 336 | Same thinking can be applied to Linux systems. 337 | 338 | Usually commands like `free` report free/unused, used, buffers, cache memory kinds. 339 | 340 | ``` 341 | $ free 342 | total used free shared buffers cached 343 | Mem: 1016464 900236 116228 21048 93448 241544 344 | -/+ buffers/cache: 565244 451220 345 | Swap: 1046524 141712 904812 346 | ``` 347 | 348 | Second line indicates available memory (free + buffers + cache), with an assumption that buffers and cache can be 100% reclaimed in case of need. 349 | 350 | However, we're not using free, because its output varies per system. For example on RHEL7, there is no `-/+ buffers/cache`, and `available` memory is reported in different way. We read directly from `/proc/meminfo` 351 | 352 | ``` 353 | $ cat /proc/meminfo 354 | 355 | MemTotal: 1016232 kB 356 | MemFree: 152672 kB 357 | MemAvailable: 637832 kB 358 | Buffers: 0 kB 359 | Cached: 529040 kB 360 | ``` 361 | 362 | `tmux-plugin-sysstat` uses following formula: 363 | 364 | ``` 365 | free/available = MemAvailable; // if MemAvailable present 366 | free/available = MemFree + Buffers + Cached; 367 | used = MemTotal - free/avaialble 368 | ``` 369 | 370 | Using `MemAvailable` is more accurate way of getting available memory, rather than manual calculation `free + buffers + cache`, because the assumption that `buffers + cache` can be 100% reclaimed for new application needs might be wrong. When using `MemAvailable`, OS calculates available memory for you, which is apparently better and accurate approach. 371 | 372 | See [this topic](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773) on more reasoning about `MemAvailable` field. 373 | -------------------------------------------------------------------------------- /screenshots/cpu_thresholds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samoshkin/tmux-plugin-sysstat/29e150f403151f2341f3abcb2b2487a5f011dd23/screenshots/cpu_thresholds.png -------------------------------------------------------------------------------- /screenshots/intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samoshkin/tmux-plugin-sysstat/29e150f403151f2341f3abcb2b2487a5f011dd23/screenshots/intro.png -------------------------------------------------------------------------------- /scripts/cpu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -u 4 | set -e 5 | 6 | LC_NUMERIC=C 7 | 8 | CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 9 | source "$CURRENT_DIR/helpers.sh" 10 | 11 | cpu_tmp_dir=$(tmux show-option -gqv "@sysstat_cpu_tmp_dir") 12 | 13 | cpu_view_tmpl=$(get_tmux_option "@sysstat_cpu_view_tmpl" 'CPU:#[fg=#{cpu.color}]#{cpu.pused}#[default]') 14 | 15 | cpu_medium_threshold=$(get_tmux_option "@sysstat_cpu_medium_threshold" "30") 16 | cpu_stress_threshold=$(get_tmux_option "@sysstat_cpu_stress_threshold" "80") 17 | 18 | cpu_color_low=$(get_tmux_option "@sysstat_cpu_color_low" "green") 19 | cpu_color_medium=$(get_tmux_option "@sysstat_cpu_color_medium" "yellow") 20 | cpu_color_stress=$(get_tmux_option "@sysstat_cpu_color_stress" "red") 21 | 22 | get_cpu_color(){ 23 | local cpu_used=$1 24 | 25 | if fcomp "$cpu_stress_threshold" "$cpu_used"; then 26 | echo "$cpu_color_stress"; 27 | elif fcomp "$cpu_medium_threshold" "$cpu_used"; then 28 | echo "$cpu_color_medium"; 29 | else 30 | echo "$cpu_color_low"; 31 | fi 32 | } 33 | 34 | print_cpu_usage() { 35 | local cpu_pused=$(get_cpu_usage_or_collect) 36 | local cpu_color=$(get_cpu_color "$cpu_pused") 37 | 38 | local cpu_view="$cpu_view_tmpl" 39 | cpu_view="${cpu_view//'#{cpu.pused}'/$(printf "%.1f%%" "$cpu_pused")}" 40 | cpu_view="${cpu_view//'#{cpu.color}'/$(echo "$cpu_color" | awk '{ print $1 }')}" 41 | cpu_view="${cpu_view//'#{cpu.color2}'/$(echo "$cpu_color" | awk '{ print $2 }')}" 42 | cpu_view="${cpu_view//'#{cpu.color3}'/$(echo "$cpu_color" | awk '{ print $3 }')}" 43 | 44 | echo "$cpu_view" 45 | } 46 | 47 | get_cpu_usage_or_collect() { 48 | local collect_cpu_metric="$cpu_tmp_dir/cpu_collect.metric" 49 | 50 | # read cpu metric from file, otherwise 0 as a temporary null value, until first cpu metric is collected 51 | [ -f "$collect_cpu_metric" ] && cat "$collect_cpu_metric" || echo "0.0" 52 | 53 | start_cpu_collect_if_required >/dev/null 2>&1 54 | } 55 | 56 | start_cpu_collect_if_required() { 57 | local collect_cpu_pidfile="$cpu_tmp_dir/cpu_collect.pid" 58 | 59 | # check if cpu collect process is running, otherwise start it in background 60 | if [ -f "$collect_cpu_pidfile" ] && ps -p "$(cat "$collect_cpu_pidfile")" > /dev/null 2>&1; then 61 | return; 62 | fi 63 | 64 | jobs >/dev/null 2>&1 65 | "$CURRENT_DIR/cpu_collect.sh" &>/dev/null & 66 | if [ -n "$(jobs -n)" ]; then 67 | echo "$!" > "${collect_cpu_pidfile}" 68 | else 69 | echo "Failed to start CPU collect job" >&2 70 | exit 1 71 | fi 72 | } 73 | 74 | main(){ 75 | print_cpu_usage 76 | } 77 | 78 | main 79 | -------------------------------------------------------------------------------- /scripts/cpu_collect.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | LC_NUMERIC=C 4 | 5 | set -u 6 | set -e 7 | 8 | CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 9 | source "$CURRENT_DIR/helpers.sh" 10 | 11 | refresh_interval=$(get_tmux_option "status-interval" "5") 12 | samples_count="60" 13 | cpu_metric_file="$(get_tmux_option "@sysstat_cpu_tmp_dir" "/dev/null")/cpu_collect.metric" 14 | 15 | get_cpu_usage() { 16 | if is_osx; then 17 | if command_exists "iostat"; then 18 | iostat -w "$refresh_interval" -c "$samples_count" \ 19 | | stdbuf -o0 awk 'NR > 2 { print 100-$(NF-3); }' 20 | else 21 | top -l "$samples_count" -s "$refresh_interval" -n 0 \ 22 | | sed -u -nr '/CPU usage/s/.*,[[:space:]]*([0-9]+[.,][0-9]*)%[[:space:]]*idle.*/\1/p' \ 23 | | stdbuf -o0 awk '{ print 100-$0 }' 24 | fi 25 | elif ! command_exists "vmstat"; then 26 | if is_freebsd; then 27 | vmstat -n "$refresh_interval" -c "$samples_count" \ 28 | | stdbuf -o0 awk 'NR>2 {print 100-$(NF-0)}' 29 | else 30 | vmstat -n "$refresh_interval" "$samples_count" \ 31 | | stdbuf -o0 awk 'NR>2 {print 100-$(NF-2)}' 32 | fi 33 | else 34 | if is_freebsd; then 35 | top -d"$samples_count" \ 36 | | sed -u -nr '/CPU:/s/.*,[[:space:]]*([0-9]+[.,][0-9]*)%[[:space:]]*id.*/\1/p' \ 37 | | stdbuf -o0 awk '{ print 100-$0 }' 38 | else 39 | top -b -n "$samples_count" -d "$refresh_interval" \ 40 | | sed -u -nr '/%Cpu/s/.*,[[:space:]]*([0-9]+[.,][0-9]*)[[:space:]]*id.*/\1/p' \ 41 | | stdbuf -o0 awk '{ print 100-$0 }' 42 | fi 43 | fi 44 | } 45 | 46 | main() { 47 | get_cpu_usage | while read -r value; do 48 | echo "$value" | tee "$cpu_metric_file" 49 | done 50 | } 51 | 52 | main 53 | 54 | -------------------------------------------------------------------------------- /scripts/helpers.sh: -------------------------------------------------------------------------------- 1 | 2 | get_tmux_option() { 3 | local option="$1" 4 | local default_value="$2" 5 | local option_value="$(tmux show-option -gqv "$option")" 6 | if [ -z "$option_value" ]; then 7 | echo "$default_value" 8 | else 9 | echo "$option_value" 10 | fi 11 | } 12 | 13 | set_tmux_option() { 14 | local option="$1" 15 | local value="$2" 16 | tmux set-option -gq "$option" "$value" 17 | } 18 | 19 | is_osx() { 20 | [ $(uname) == "Darwin" ] 21 | } 22 | 23 | is_linux(){ 24 | [ $(uname -s) == "Linux" ] 25 | } 26 | 27 | is_freebsd() { 28 | [ $(uname) == FreeBSD ] 29 | } 30 | 31 | command_exists() { 32 | local command="$1" 33 | type "$command" >/dev/null 2>&1 34 | } 35 | 36 | # because bash does not support floating-point math 37 | # but awk does 38 | calc() { 39 | local stdin; 40 | read -d '' -u 0 stdin; 41 | awk "BEGIN { print $stdin }"; 42 | } 43 | 44 | # "<" math operator which works with floats, once again based on awk 45 | fcomp() { 46 | awk -v n1="$1" -v n2="$2" 'BEGIN {if (n1