├── 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 | 
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 | CPU |
77 |
78 | #{cpu.color} |
79 | main metric color |
80 |
81 |
82 | #{cpu.pused} |
83 | CPU usage percentage |
84 |
85 |
86 |
87 |
88 | Memory |
89 |
90 | #{mem.color} |
91 | main metric color |
92 |
93 |
94 | #{mem.free} |
95 | free/available memory |
96 |
97 |
98 | #{mem.pfree} |
99 | free memory percentage against total |
100 |
101 |
102 | #{mem.used} |
103 | used memory |
104 |
105 |
106 | #{mem.pused} |
107 | used memory percentage against total |
108 |
109 |
110 | #{mem.total} |
111 | total installed memory |
112 |
113 |
114 |
115 |
116 | Swap |
117 |
118 | #{swap.color} |
119 | main swap metric color |
120 |
121 |
122 | #{swap.free} |
123 | free swap memory |
124 |
125 |
126 | #{swap.pfree} |
127 | free swap memory percentage against total swap space |
128 |
129 |
130 | #{swap.used} |
131 | used swap memory |
132 |
133 |
134 | #{swap.pused} |
135 | used swap memory percentage against total swap space |
136 |
137 |
138 | #{swap.total} |
139 | total swap space |
140 |
141 |
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 | Threshold |
160 | CPU |
161 | Memory |
162 | Swap |
163 | Default color |
164 |
165 |
166 | low |
167 | x < 30% |
168 | x < 75% |
169 | x < 25% |
170 | green |
171 |
172 |
173 | medium |
174 | 30% < x < 80% |
175 | 75% < x < 90% |
176 | 25% < x < 75% |
177 | yellow |
178 |
179 |
180 | high |
181 | x > 80% |
182 | x > 90% |
183 | x > 75% |
184 | red |
185 |
186 |
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 | 
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