aboutsummaryrefslogtreecommitdiffstats
path: root/widgets/gpu.lua
blob: abac856b71a1799f03274da729d37da766df7a33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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
114
115
116
117
118
119
120
121
-- GPU: Arc B580 temperatures, VRAM in use and fan speed.
--
-- No utilisation figure, deliberately. The xe driver exposes no
-- gpu_busy_percent and intel_gpu_top refuses the device outright ("Detected Xe
-- device which is not supported"). VRAM comes from DRM fdinfo, the per-client
-- counters gputop itself reads (see data.drm_vram).
--
-- The integrated AMD GPU does expose gpu_busy_percent and is deliberately not
-- shown: the discrete card is the one in use.

local card = require 'lib.card'
local data = require 'lib.data'

local M = {}

-- The B580's VRAM. fdinfo reports what clients hold, not the card's size, and
-- xe puts no capacity in sysfs. ponytail: hardcoded like the 'ARC B580' label.
local VRAM_TOTAL = 12 * 1024 ^ 3

-- fdinfo is a popen over every fd on the system, so sample it every ~2s, not
-- every frame.
local gate = data.new_gate(1.9)
local vram

-- chip, file, label, warn, crit. Package (temp2) is the headline above, so it
-- is not repeated here; these are the rows drawn under it.
local TEMPS = {
  { 'xe', 'temp3_input', 'VRAM', 80, 90 },
}

function M.draw(cr, rect, colors)
  local inner = card.card(cr, rect, colors)
  local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end

  if gate() then vram = data.drm_vram() end

  local pkg = data.sensor('xe', 'temp2_input')
  local big_txt = pkg and string.format('%d\u{00B0}', pkg) or '--'

  -- Fluid type. One base size S drives every element. The cell's height sets
  -- how large the content grows; the measured width fit pulls S back down
  -- only where the header or a row would overrun the card. So a tall cell
  -- gets larger type instead of empty space, and nothing ever collides.
  local LABEL_F, ROW_F = 0.32, 0.34
  local row_n = #TEMPS + 2                            -- VRAM + MEM + FAN
  local S_h = inner.h * 0.94 / (2.088 + row_n * 0.646 + 0.3)  -- + MEM bar
  local groups = {
    { { card.title_str(card.ICON.gpu, 'GPU'), card.FONT_MONO, LABEL_F },
      { big_txt, card.FONT_HEAVY, 1.0 } },
    { { 'ARC B580', card.FONT_MONO, LABEL_F } },
  }
  for _, t in ipairs(TEMPS) do
    groups[#groups + 1] = { { t[3], card.FONT_MONO, ROW_F },
                            { '888\u{00B0}', card.FONT_MONO, ROW_F } }
  end
  groups[#groups + 1] = { { 'MEM', card.FONT_MONO, ROW_F },
                          { '100%', card.FONT_MONO, ROW_F } }
  groups[#groups + 1] = { { 'FAN', card.FONT_MONO, ROW_F },
                          { '8888 RPM', card.FONT_MONO, ROW_F } }
  local S = clamp(math.min(S_h, card.fit_unit(cr, inner.w * 0.96, groups, 100)), 10, 72)
  local label_size = S * LABEL_F
  local row_size   = S * ROW_F

  local y = inner.y

  -- The package temperature is the headline, since there is no load to show.
  -- It shares the header line with the label, set on the same baseline and
  -- right-aligned, so the card's width carries the number rather than leaving
  -- the right half empty under a left-set value.
  card.font(cr, card.FONT_HEAVY, S, false)
  card.rgba(cr, pkg and card.threshold(pkg, 75, 85, colors) or colors.label)
  card.text_right(cr, inner.x + inner.w, y + S, big_txt)

  card.font(cr, card.FONT_MONO, label_size, false)
  card.title(cr, inner.x, y + label_size, card.ICON.gpu, 'GPU', colors)

  card.rgba(cr, colors.value)
  card.text(cr, inner.x, y + S + label_size * 1.7, 'ARC B580')

  local ey = y + S + label_size * 3.4
  local step = row_size * 1.9

  card.font(cr, card.FONT_MONO, row_size, false)
  for _, t in ipairs(TEMPS) do
    if ey + step > inner.y + inner.h then break end
    local v = data.sensor(t[1], t[2])
    card.rgba(cr, colors.label)
    card.text(cr, inner.x, ey, t[3])
    card.rgba(cr, v and card.threshold(v, t[4], t[5], colors) or colors.label)
    card.text_right(cr, inner.x + inner.w, ey,
      v and string.format('%d\u{00B0}', v) or '--')
    ey = ey + step
  end

  -- VRAM as a fraction of the card's, a bar under the row like RAM in system.
  local bar_h = S * 0.12
  if ey + step + bar_h + S * 0.1 <= inner.y + inner.h then
    local frac = vram and vram / VRAM_TOTAL
    local colour = frac and card.threshold(frac * 100, 25, 75, colors) or colors.label
    card.rgba(cr, colors.label)
    card.text(cr, inner.x, ey, 'MEM')
    card.rgba(cr, colour)
    card.text_right(cr, inner.x + inner.w, ey,
      frac and string.format('%d%%', math.floor(frac * 100 + 0.5)) or '--')
    card.bar(cr, inner.x, ey + row_size * 0.6, inner.w, bar_h, frac, colour, colors)
    ey = ey + step + bar_h + S * 0.1
  end

  -- Fan RPM is not a temperature, so it carries no threshold colour: a fast
  -- fan is the cooling working, not a fault.
  if ey + step <= inner.y + inner.h then
    local rpm = data.sensor_raw('xe', 'fan1_input')
    card.rgba(cr, colors.label)
    card.text(cr, inner.x, ey, 'FAN')
    card.rgba(cr, colors.value)
    card.text_right(cr, inner.x + inner.w, ey,
      rpm and string.format('%d RPM', rpm) or '--')
  end
end

return M