summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndrew Cady <d@cryptonomic.net>2022-11-29 12:00:08 -0500
committerAndrew Cady <d@cryptonomic.net>2022-11-29 12:00:08 -0500
commit61564e4d18058031d96e207862f72f0628934ea3 (patch)
tree1a43ee54623384f0b09387c68062905aeefe36f5 /src
parent42a4367a83645b2bca1f0a4d48917fb9ec3bbf5b (diff)
move files to src; add README.txt
Diffstat (limited to 'src')
-rwxr-xr-xsrc/cgroup-show-each-new-process51
-rwxr-xr-xsrc/fireslay38
-rwxr-xr-xsrc/ioslay-firefox87
-rwxr-xr-xsrc/ioslay-mgr.sh105
-rwxr-xr-xsrc/slice47
-rwxr-xr-xsrc/sliceuser13
-rwxr-xr-xsrc/sliceweasel15
-rwxr-xr-xsrc/sliceweasel.lib.sh154
-rw-r--r--src/your-fired.sh136
9 files changed, 646 insertions, 0 deletions
diff --git a/src/cgroup-show-each-new-process b/src/cgroup-show-each-new-process
new file mode 100755
index 0000000..d1473f1
--- /dev/null
+++ b/src/cgroup-show-each-new-process
@@ -0,0 +1,51 @@
1#!/bin/bash
2cgroup=${1:-firefox~$(id -u)}
3
4show_new_pids()
5{
6 if [ -e "$procs" ]
7 then
8 sort < "$procs" > pids
9 if [ -e old-pids ]
10 then
11 comm -13 old-pids pids
12 else
13 cat pids
14 fi
15 mv pids old-pids
16 fi
17}
18
19show_pid_comm()
20{
21 local pid comm
22 pid=$1
23 if 2>/dev/null read comm < /proc/$pid/comm
24 then
25 echo "$pid $comm"
26 else
27 echo $pid '(disappeared)' >&2
28 fi
29}
30
31each_line()
32{
33 local line
34 while read line
35 do "$@" "$line"
36 done
37}
38
39RUNTIME_DIR=$(mktemp -d) || exit
40trap 'cd /; rm -rf "$RUNTIME_DIR"' EXIT
41cd "$RUNTIME_DIR"
42
43enable -f /usr/lib/bash/sleep sleep
44procs=/sys/fs/cgroup/$cgroup/cgroup.procs
45
46while true
47do
48 show_new_pids
49 sleep 1
50done | each_line show_pid_comm
51
diff --git a/src/fireslay b/src/fireslay
new file mode 100755
index 0000000..3c68ad5
--- /dev/null
+++ b/src/fireslay
@@ -0,0 +1,38 @@
1#!/bin/sh
2set -e
3. sliceweasel.lib.sh
4
5is_web_content()
6{
7 local comm state
8 read comm < /proc/"$1"/comm && [ "$comm" = 'Web Content' ] &&
9 read _ _ state _ < /proc/"$1"/stat && [ "$state" != Z ] || return
10}
11
12web_content_pids()
13{
14 while read pid
15 do
16 if is_web_content $pid
17 then
18 echo $pid
19 fi
20 done < $FIREFOX_GROUP_PROCS
21}
22
23FIREFOX_GROUP_PROCS=$(get_firefox_cgroup_procs)
24
25[ -e "$FIREFOX_GROUP_PROCS" ] || die "Firefox group not found ($FIREFOX_GROUP_PROCS)"
26
27set -- $(web_content_pids)
28
29if [ $# = 0 ]
30then
31 exit
32fi
33
34(
35 set -x
36 ps $* >&2
37 kill $*
38)
diff --git a/src/ioslay-firefox b/src/ioslay-firefox
new file mode 100755
index 0000000..41aa6a4
--- /dev/null
+++ b/src/ioslay-firefox
@@ -0,0 +1,87 @@
1#!/bin/bash
2sum()
3{
4 local total=0 n
5 for n in $*
6 do
7 total=$((total + n))
8 done
9 echo $total
10}
11
12last()
13{
14 local n=$1
15 shift
16 set -- $*
17 while [ $# -ge "$n" ]
18 do
19 shift
20 done
21 printf '%s\n' "$*"
22}
23
24iotop_reader()
25{
26 log=
27 pct_log=
28 while read line
29 do
30 case "$line" in
31 "Total DISK"*)
32 pct=$(sum $pct_log)
33 [ "$pct" -ge 85 ] && over=y || over=
34 pct_log=
35 if [ "$over" ]
36 then
37 log=$(last 10 $log 1)
38 over=
39 sum=$(sum $log)
40 if [ "$(sum $log)" -gt 4 ]
41 then
42 log= # this is year zero; all past history is erased.
43 (
44 set -x
45 kill $pids
46 )
47 fi
48 else
49 log=$(last 10 $log 0)
50 fi
51 case "$log" in
52 *1* )
53 echo IO '!!' $log >&2
54 ;;
55 # * )
56 # echo IO OK $log >&2
57 # ;;
58 esac
59 ;;
60 "Current DISK"*) continue ;;
61 *)
62 set -- $line
63 pct=${10%.*}
64 pct_log="$pct_log $pct"
65 ;;
66 esac
67 done
68}
69
70sudo_iotop()
71{
72 if [ "$UID" = 0 ]
73 then
74 command iotop "$@"
75 else
76 sudo iotop "$@"
77 fi
78}
79
80if [ $# -le 0 ]
81then
82 exit
83fi
84
85pids=$*
86
87sudo_iotop -qq -b $(printf '\55\55pid=%s\n' "$@") | iotop_reader
diff --git a/src/ioslay-mgr.sh b/src/ioslay-mgr.sh
new file mode 100755
index 0000000..56ab849
--- /dev/null
+++ b/src/ioslay-mgr.sh
@@ -0,0 +1,105 @@
1#!/bin/bash
2
3UNIT_NAME=ioslay
4if [ "$1" = launch-unit ]
5then
6 if systemctl --user is-active "$UNIT_NAME"
7 then systemctl --user restart "$UNIT_NAME"
8 else systemd-run --user -u "$UNIT_NAME" "$0"
9 fi
10 exit
11fi
12
13if [ -e /usr/lib/bash/sleep ]
14then
15 enable -f /usr/lib/bash/sleep sleep
16fi
17
18export NOTICE=y
19noticeLOG() { [ "$NOTICE" ] || return; echo "Notice: $*" >&2; }
20debugLOG() { [ "$DBG" ] || return; echo "Debug: $*" >&2; }
21
22. sliceweasel.lib.sh
23
24vkill()
25{
26 if [ $# = 0 ]
27 then
28 return
29 fi
30 (
31 if [ "$(id -u)" = 0 ]
32 then sudo=
33 else sudo=sudo
34 fi
35 set -x
36 ps u "$@"
37 $sudo kill "$@"
38 )
39}
40
41slay_slayer()
42{
43 if [ "$ioslay" ]
44 then
45 children=$(for pid in $ioslay; do pgrep -P $ioslay; done)
46 grandchildren=$(for pid in $children; do pgrep -P $pid; done)
47 vkill $ioslay $children $grandchildren
48 fi
49}
50
51GROUP_BASENAME=firefox
52
53group_procs=$(get_firefox_cgroup_procs)
54
55ioslay=
56lastprocs=
57SIGNALLED=
58trap 'SIGNALLED=y' SIGINT SIGTERM SIGHUP
59while [ ! "$SIGNALLED" ]
60do
61 if ! [ -e "$group_procs" ]
62 then
63 [ "$warned" ] || echo "Warning: firefox not running or cgroup not found" >&2
64 warned=y
65 else
66 if [ "$warned" ]
67 then
68 echo "Found firefox cgroup: $group_procs" >&2
69 warned=
70 fi
71 read -N 1000100 procs < "$group_procs"
72 if [ "$procs" ]
73 then
74 set --
75 for pid in $procs
76 do
77 read comm < /proc/$pid/comm
78 case "$comm" in
79 'Isolated Web Content' | 'Web Content' | 'Isolated Web Co')
80 set -- "$@" "$pid"
81 debugLOG "accept /proc/$pid/comm $comm"
82 ;;
83 *)
84 debugLOG "reject /proc/$pid/comm $comm"
85 esac
86 done
87
88 # echo "pids: ($*|$(echo $procs))" >&2
89 if [ "$lastargs" != "$*" ]
90 then
91 slay_slayer
92 wait $ioslay
93 if [ $# -gt 0 ]
94 then
95 ioslay-firefox "$@" &
96 ioslay=$!
97 noticeLOG "Launched ioslay-firefox[$ioslay] $*"
98 fi
99 fi
100 lastargs=$*
101 fi
102 fi
103 sleep 1
104done
105slay_slayer
diff --git a/src/slice b/src/slice
new file mode 100755
index 0000000..44ae13e
--- /dev/null
+++ b/src/slice
@@ -0,0 +1,47 @@
1#!/bin/bash
2default_percent='50'
3keep_env=(XAUTHORITY DISPLAY)
4set -e
5
6usage()
7{
8 cat <<EOF
9Usage: $0 [percentage]% [--] command [arguments...]
10
11 The [percentage] argument must end with a literal '%'.
12
13 E.g.:
14
15 slice 50% firefox
16
17 The command cannot start with a literal '-'.
18EOF
19}
20
21percent=$1
22case "$1" in
23 --) percent=$default_percent ;;
24 '' | -*) usage >&2; exit 1;;
25 *.*%) percent=${1%.*} ;;
26 *%) percent=${1%\%} ;;
27esac
28[ "$percent" -ge 0 ]
29[ "$percent" -le 100 ]
30
31shift
32case "$1" in
33 --) shift ;;
34esac
35unit_name=$1
36
37keep_env_opts=()
38for v in "${keep_env[@]}"
39do
40 keep_env_opts+=(-E "${!v}")
41done
42
43systemd-run -u "$unit_name" \
44 "${keep_env_opts[@]}"
45 "$0" "$@"
46 "$@"
47
diff --git a/src/sliceuser b/src/sliceuser
new file mode 100755
index 0000000..16d441f
--- /dev/null
+++ b/src/sliceuser
@@ -0,0 +1,13 @@
1#!/bin/sh
2. sliceweasel.lib.sh
3
4set -e
5IO_ROOT_DIR=$HOME/.cache/mozilla/firefox/
6[ -d "$IO_ROOT_DIR" ]
7[ "$(id -un)" = 0 ] && AS_ROOT= || AS_ROOT='sudo --'
8
9group=$(get_current_group)
10set -x
11set_max_ratio "$group" memory 3/4
12set_max_ratio "$group" io 8/10
13
diff --git a/src/sliceweasel b/src/sliceweasel
new file mode 100755
index 0000000..f9df2c8
--- /dev/null
+++ b/src/sliceweasel
@@ -0,0 +1,15 @@
1#!/bin/sh
2. sliceweasel.lib.sh
3
4set -e
5IO_ROOT_DIR=$HOME/.cache/mozilla/firefox/
6[ -d "$IO_ROOT_DIR" ]
7[ "$(id -un)" = 0 ] && AS_ROOT= || AS_ROOT='sudo --'
8
9group=/sys/fs/cgroup/user.slice/user-$(id -u).slice/firefox
10join_group "$group"
11set_max_ratio "$group" memory 1/2
12set_max_ratio "$group" io 7/10
13exec firefox "$@"
14
15
diff --git a/src/sliceweasel.lib.sh b/src/sliceweasel.lib.sh
new file mode 100755
index 0000000..549e1e8
--- /dev/null
+++ b/src/sliceweasel.lib.sh
@@ -0,0 +1,154 @@
1#!/bin/sh
2die()
3{
4 printf '%s: Error: %s\n' "$0" "$*" >&2
5 exit 1
6}
7
8get_age()
9{
10 local mtime now
11 mtime=$(stat --format=%Y "$1") || return
12 now=$(date +%s) || return
13 echo $(( now - mtime ))
14}
15
16get_filesystem()
17{
18 echo /
19}
20
21get_total_memory()
22{
23 free -b | {
24 read _
25 read _ total _
26 echo $total
27 }
28}
29
30math()
31{
32 printf '%s\n' "$*" | bc -lq
33}
34
35get_total_io()
36{
37 results=$IO_ROOT_DIR/io.test.result
38 zeroes=$IO_ROOT_DIR/io.test
39 if [ -e "$results" ]
40 then
41 age=$(get_age "$results") || return
42 if [ "$age" -gt $((60 * 60 * 24 * 7)) ]
43 then
44 rm "$results"
45 fi
46 fi
47
48 if ! [ -e "$results" ]
49 then
50 MEGS=128
51 then=$(date +%s.%N) || return
52 dd if=/dev/zero of="$zeroes" bs=${MEGS}M count=1 || return
53 now=$(date +%s.%N) || return
54 rm "$zeroes"
55 speed=$(math $now - $then / $((MEGS * 1024 * 1024))) || return
56 echo $speed > "$results"
57 fi
58 read total < "$results" || return
59 echo ${total%.*}
60}
61
62root_write()
63{
64 $AS_ROOT sh -c 'cat > "$1"' sh "$1" || true
65}
66
67join_group()
68{
69 GROUP_DIR="$1"
70 [ -d "$GROUP_DIR" ] || $AS_ROOT mkdir "$GROUP_DIR"
71 echo $$ | root_write "$GROUP_DIR"/cgroup.procs
72}
73
74add_subtree_controller()
75{
76 local group="$1" controller="$2" control_file
77 control_file="$group/cgroup.subtree_control"
78
79 [ "$group" ] && [ "$controller" ] && [ -d "$group" ] && [ -e "$control_file" ] || return
80
81 case "$group" in
82 /sys/fs/cgroup) ;;
83 *) add_subtree_controller "$(realpath "$group/..")" "$2" ;;
84 esac
85
86 if ! grep -qe "\\b${controller}\\b" "$control_file"
87 then
88 echo +"$controller" | root_write "$control_file"
89 fi
90}
91
92set_max()
93{
94 max_file=$group/$2.max
95 if [ ! -e "$max_file" ]
96 then
97 add_subtree_controller "$1"/.. "$2"
98 [ -e "$max_file" ] || return
99 fi
100 printf '%s\n' "$3" | root_write "$max_file"
101}
102
103set_max_ratio()
104{
105 local group="$1" controller="$2" ratio="$3" limit
106 total=$(get_total_$controller)
107 n=${ratio%%/*}
108 d=${ratio#$n/}
109 [ "$d" = "${d%/*}" ] || return
110 limit=$(( total * n / d ))
111 [ "$limit" ] || return
112 [ "$limit" -gt 0 ] || return
113 case "$controller" in
114 io)
115 fsroot=$(get_filesystem "$IO_ROOT_DIR") || return
116 # TODO: Do not use lsblk, because it fails to report the correct
117 # MOUNTPOINT when a bind mount is present.
118 majmin=$(lsblk -o 'MOUNTPOINT,MAJ:MIN' | sed -ne "s?^$fsroot *??p") || return
119 case "$majmin" in
120 *:*) ;;
121 *) echo "Error: majmin=$majmin" >&2; return 1 ;;
122 esac
123 set_max "$group" "$controller" "$majmin wbps=$limit rbps=$limit"
124 ;;
125 *) set_max "$group" "$controller" "$limit"
126 esac
127}
128
129get_uid()
130{
131 (
132 if [ "$SUDO_USER" ]
133 then
134 IFS=:
135 set -- $(getent passwd "${SUDO_USER}") || return
136 echo $3
137 else
138 id -u
139 fi
140 )
141}
142
143get_firefox_cgroup_procs()
144{
145 uid=$(get_uid)
146 [ "$uid" ] || return
147 echo /sys/fs/cgroup/user.slice/user-$uid.slice/user@$uid.service/app.slice/firefox.service/cgroup.procs
148}
149
150get_current_group()
151{
152 read g < /proc/$$/cgroup
153 echo /sys/fs/cgroup/${g#0::/}
154}
diff --git a/src/your-fired.sh b/src/your-fired.sh
new file mode 100644
index 0000000..59efaf7
--- /dev/null
+++ b/src/your-fired.sh
@@ -0,0 +1,136 @@
1#!/bin/bash
2
3WEB_CONTENT_OOM_ADJ=500
4FIREFIX_REPEAT_INTERVAL=5
5
6ploop()
7{
8 local p
9 for p in /proc/[0-9]*
10 do
11 "$@"
12 done
13}
14
15match_parent()
16{
17 # This isn't a file to which we will write (we write to the child's
18 # oom_score_adj), but this ignores processes we don't own.
19 2>/dev/null [ -w "$p"/oom_score_adj ] || return
20
21 local stat
22 2>/dev/null read stat < "$p"/stat || return
23 case "$stat" in
24 *") "?" $1 "*) echo "${p##*/}" ;;
25 esac
26}
27
28match_comm()
29{
30 local comm
31 2>/dev/null read comm < "$p"/comm || return
32 [ "$comm" = "$1" ] && echo "${p##*/}"
33}
34
35firefix()
36{
37 parent=$1
38 read parent_oom_score < /proc/$parent/oom_score
39 read parent_oom_score_adj < /proc/$parent/oom_score_adj
40
41 for child in $(ploop match_parent $parent)
42 do
43 2>/dev/null read comm < /proc/$child/comm || continue
44 [ "$comm" = 'Web Content' ] || continue
45
46 read oom_score < /proc/$child/oom_score
47 read oom_score_adj < /proc/$child/oom_score_adj
48
49 want_adj=$((parent_oom_score_adj + ${WEB_CONTENT_OOM_ADJ:-500}))
50 if [ "$want_adj" -gt "$oom_score_adj" ]
51 then
52 printf 'Setting oom_score_adj for pid=%d to %d (from %d)\n' $child $want_adj $oom_score_adj >&2
53 printf '%d\n' "$want_adj" > /proc/$child/oom_score_adj
54 fi
55 done
56}
57
58firefix_all()
59{
60 for parent in $(ploop match_comm firefox-bin)
61 do
62 firefix $parent &
63 done
64 wait
65}
66
67firefix_all_forever()
68{
69 while true
70 do
71 firefix_all
72 sleep ${FIREFIX_REPEAT_INTERVAL:-5}
73 done
74}
75
76unit_file()
77{
78 cat <<EOF
79[Unit]
80Description=$1
81[Service]
82ExecStart=$2
83[Install]
84WantedBy=default.target
85EOF
86}
87
88install_self()
89{
90 unit_file_name=$1
91 unit_executable=$2
92 unit_args=$3
93 unit_desc=$4
94
95 [ -e "$unit_executable" ] || return
96
97 if [ "$(id -u)" = 0 ]
98 then
99 service_dir=/etc/systemd/system
100 systemctl=systemctl
101 instdir=/usr/local/bin
102 else
103 service_dir=$HOME/.config/systemd/user
104 systemctl='systemctl --user'
105 instdir=$HOME/.local/bin
106 fi
107 unit_executable_installed=$instdir/${unit_executable##*/}
108
109 [ "$unit_executable_installed" -ef "$unit_executable" ] ||
110 install -D -t "$instdir" "$unit_executable" || return
111
112 [ -d "$service_dir" ] || mkdir -p "$service_dir" || return
113
114 unit_file=${service_dir}/${unit_file_name}.service
115 unit_file "$unit_desc" "$unit_executable_installed $unit_args" > "$unit_file"
116
117 $systemctl daemon-reload
118 $systemctl enable "$unit_file_name"
119 $systemctl restart "$unit_file_name"
120 $systemctl status "$unit_file_name"
121}
122
123usage()
124{
125 echo "Usage: $0 <install|once|forever>" >&2
126}
127
128enable -f /usr/lib/bash/sleep sleep 2>/dev/null || true
129
130case "$*" in
131 forever) firefix_all_forever ;;
132 install) install_self firefixer "$0" forever 'Firefixer - adjust firefox OOM scores';;
133 once) firefix_all ;;
134 -h|--help) usage; exit ;;
135 *) usage; exit 1 ;;
136esac