chopstick
All widgets

Activity Monitor

Comes with Chopstick

CPU and memory use with the busiest processes, like a glance at Activity Monitor; opens the full app from the card.

Already installed? It's in your widget library.

Type it in the notch's search

  • Show CPU and memory
  • Open Activity Monitor

What it runs

A widget is a folder of plain files. Here is all of its code.

widget.json
{
  "id": "activity-monitor",
  "name": "Activity Monitor",
  "icon": "icon.png",
  "description": "CPU and memory use with the busiest processes, like a glance at Activity Monitor; opens the full app from the card.",
  "anytime": true,
  "watch": [
    {
      "script": "stats.sh",
      "every": 3,
      "as": "sys"
    }
  ],
  "initial": "glance",
  "states": {
    "glance": {
      "compact": {
        "leading": {
          "symbol": "cpu",
          "tint": "{{sys.cpu_tint | gray}}"
        },
        "trailing": {
          "text": "{{sys.cpu_label | …}}"
        }
      },
      "expanded": {
        "size": "regular",
        "header": {
          "symbol": "cpu",
          "title": "Activity Monitor"
        },
        "blocks": [
          {
            "row": [
              {
                "ring": {
                  "value": "{{sys.cpu | 0}}",
                  "max": "100",
                  "label": "{{sys.cpu_label}}",
                  "size": "medium",
                  "tint": "{{sys.cpu_tint | cyan}}"
                }
              },
              {
                "stack": [
                  { "text": "CPU", "style": "body" },
                  { "note": "{{sys.cpu_label | …}} in use" }
                ],
                "align": "leading",
                "spacing": "tight"
              },
              "spacer",
              {
                "ring": {
                  "value": "{{sys.mem_pct | 0}}",
                  "max": "100",
                  "label": "{{sys.mem_pct}}%",
                  "size": "medium",
                  "tint": "{{sys.mem_tint | green}}"
                }
              },
              {
                "stack": [
                  { "text": "Memory", "style": "body" },
                  { "note": "{{sys.mem_used | …}} of {{sys.mem_total}}" },
                  { "note": "Pressure {{sys.pressure | normal}}" }
                ],
                "align": "leading",
                "spacing": "tight"
              }
            ],
            "align": "center"
          },
          {
            "sparkline": {
              "from": "sys.history",
              "tint": "{{sys.cpu_tint | cyan}}"
            }
          },
          {
            "list": {
              "from": "sys.procs",
              "limit": 5,
              "title": "{{item.name}}",
              "subtitle": "{{item.cpu}} CPU · {{item.mem}}"
            }
          },
          {
            "button": {
              "symbol": "arrow.up.forward.app",
              "label": "Open Activity Monitor",
              "do": [
                { "run": "open.sh" }
              ]
            }
          }
        ]
      },
      "keys": [
        {
          "key": "cmd+o",
          "label": "Activity Monitor",
          "do": [
            { "run": "open.sh" }
          ]
        }
      ]
    }
  },
  "commands": {
    "show": {
      "description": "Show CPU and memory use and the busiest processes.",
      "phrase": "Show CPU and memory",
      "do": [
        { "go": "glance" }
      ]
    },
    "open": {
      "description": "Open the Activity Monitor app.",
      "phrase": "Open Activity Monitor",
      "do": [
        { "run": "open.sh" }
      ]
    }
  },
  "preview": {
    "states": [
      "glance"
    ],
    "data": {
      "sys": {
        "cpu": 34,
        "cpu_label": "34%",
        "cpu_tint": "cyan",
        "history": [12, 18, 15, 22, 40, 61, 48, 30, 26, 34],
        "mem_pct": 71,
        "mem_used": "17.0 GB",
        "mem_total": "24 GB",
        "pressure": "normal",
        "mem_tint": "green",
        "procs": [
          { "pid": 405, "name": "WindowServer", "cpu": "21.3%", "mem": "837 MB" },
          { "pid": 38418, "name": "Safari", "cpu": "14.4%", "mem": "1.2 GB" },
          { "pid": 0, "name": "kernel_task", "cpu": "9.4%", "mem": "31 MB" },
          { "pid": 37784, "name": "Xcode", "cpu": "6.1%", "mem": "2.4 GB" },
          { "pid": 527, "name": "Spotify", "cpu": "3.0%", "mem": "410 MB" }
        ]
      }
    }
  }
}
open.sh
#!/bin/sh
open -a "Activity Monitor"
stats.sh
#!/bin/sh
# CPU, memory and the busiest processes from a one-second `top` sample, as one JSON object.
# The last 30 CPU readings are kept in $CHOPSTICK_STORAGE for the sparkline.
OUT=$(top -l 2 -n 6 -o cpu -s 1 -stats pid,command,cpu,mem 2>/dev/null) || { echo "Couldn't read top" >&2; exit 1; }
# top's first sample counts since boot; keep the second.
OUT=$(printf '%s\n' "$OUT" | awk '/^Processes:/ { n++ } n == 2')

CPU=$(printf '%s\n' "$OUT" | awk '/^CPU usage:/ { for (i = 1; i <= NF; i++) if ($i == "idle") { sub("%", "", $(i-1)); printf "%d", 100 - $(i-1) + 0.5 } }')
[ -n "$CPU" ] || CPU=0
# Used like Activity Monitor counts it: app memory (anonymous minus purgeable), wired and compressed.
USED_GB=$(vm_stat | awk '
  /page size of/ { size = $8 }
  /^Anonymous pages/ { a = $NF } /^Pages purgeable/ { p = $NF }
  /^Pages wired down/ { w = $NF } /^Pages occupied by compressor/ { c = $NF }
  END { printf "%.1f", (a - p + w + c) * size / 1073741824 }')
TOTAL=$(( $(sysctl -n hw.memsize) / 1073741824 ))
MEM_PCT=$(awk -v u="$USED_GB" -v t="$TOTAL" 'BEGIN { printf "%d", u / t * 100 + 0.5 }')
# 1 normal, 2 warning, 4 critical
case $(sysctl -n kern.memorystatus_vm_pressure_level 2>/dev/null) in
  4) PRESSURE="critical"; MEM_TINT="red" ;;
  2) PRESSURE="high"; MEM_TINT="yellow" ;;
  *) PRESSURE="normal"; MEM_TINT="green" ;;
esac

if   [ "$CPU" -ge 80 ]; then CPU_TINT="red"
elif [ "$CPU" -ge 50 ]; then CPU_TINT="orange"
else CPU_TINT="cyan"; fi

HISTORY=""
if [ -n "$CHOPSTICK_STORAGE" ]; then
  mkdir -p "$CHOPSTICK_STORAGE"
  F="$CHOPSTICK_STORAGE/cpu"
  { cat "$F" 2>/dev/null; echo "$CPU"; } | tail -30 > "$F.new" && mv "$F.new" "$F"
  HISTORY=$(paste -sd, "$F")
fi
[ -n "$HISTORY" ] || HISTORY=$CPU

# Rows after the PID header, without top itself: "75769  swift-frontend   69.1 655M+".
# top cuts names at 16 characters, so the names come from ps where it can tell (not kernel_task).
ROWS=$(printf '%s\n' "$OUT" | awk '/^PID/ { rows = 1; next } rows && NF >= 4 && $2 != "top" { $0 = $1 " " $(NF-1) " " $NF " " substr($0, index($0, $2)); NF -= 2; print }' | head -5)
PIDS=$(printf '%s\n' "$ROWS" | awk '{ print $1 }' | paste -sd, -)
PROCS=$( { ps -o pid=,comm= -p "$PIDS" 2>/dev/null; echo "--"; printf '%s\n' "$ROWS"; } | awk '
  $0 == "--" { rows = 1; next }
  !rows { pid = $1; sub(/^ *[0-9]+ /, ""); n = split($0, parts, "/"); names[pid] = parts[n]; next }
  {
    name = $4; for (j = 5; j <= NF; j++) name = name " " $j; if ($1 in names) name = names[$1]; gsub(/["\\]/, "", name)
    mem = $3; sub(/[+-]$/, "", mem); u = substr(mem, length(mem)); v = mem + 0
    if (u == "K") v /= 1024; if (u == "G") v *= 1024; if (u == "B") v /= 1048576
    mem = v >= 1024 ? sprintf("%.1f GB", v / 1024) : sprintf("%d MB", v + 0.5)
    printf "%s{\"pid\":%d,\"name\":\"%s\",\"cpu\":\"%s%%\",\"mem\":\"%s\"}", (i++ ? "," : ""), $1, name, $2, mem
  }')

printf '{"cpu":%d,"cpu_label":"%d%%","cpu_tint":"%s","history":[%s],"mem_pct":%d,"mem_used":"%s GB","mem_total":"%d GB","pressure":"%s","mem_tint":"%s","procs":[%s]}\n' \
  "$CPU" "$CPU" "$CPU_TINT" "$HISTORY" "$MEM_PCT" "$USED_GB" "$TOTAL" "$PRESSURE" "$MEM_TINT" "$PROCS"