SWOv git · main
SWOv - window overview for sway
C 91.5% Markdown 7.6%git clone https://git.christianimmanuel.de/sway/SWOv.gitwget https://git.christianimmanuel.de/sway/SWOv/archive/SWOv.tar.gzFix cpu usage not updating
README.md | 16 ++++++++- swov.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 119 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index bb8d411..c92afe4 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,19 @@ dots filling from the bottom, relative to the busiest workspace — and prints t next to the window count. A workspace that falls empty is forgotten and starts from zero. +`--timing` prints how long each part of startup took, to stderr, if one ever +feels slow: + +``` +swov: sway socket 0.1 ms ( 0.1 total) +swov: SDL_Init 1.8 ms ( 1.9 total) +swov: font lookup 114.0 ms (123.6 total) <- fc-match, once per font +``` + +The answer fc-match gives never changes between runs, so it is kept in +`~/.cache/swov/fonts` and the second run onwards costs nothing. Deleting that +file makes swov ask again. + ```sh swov --usage # where you are, and how long each workspace has had you swov --info # every path and setting in use: binary, config, usage file, @@ -218,7 +231,8 @@ swov --info # every path and setting in use: binary, config, usage file, ## Load per workspace -A second dot scale down the *right* edge of each tile says how busy that +The numbers are re-read while the overview is open, so a build starting after +you opened it shows up. A second dot scale down the *right* edge of each tile says how busy that workspace is, mirroring the usage dots on the left: the same shape in a different colour, one for the time you have spent there and one for the work happening there now. The scale is in cores — one core kept busy is `1.0`, and `cpu_full=4` fills diff --git a/swov.c b/swov.c index 1d343ce..cb10d0c 100644 --- a/swov.c +++ b/swov.c @@ -1133,6 +1133,20 @@ static bool node_is_view(const JV *n); static char *escape_arg(const char *s); static bool ADOPT_LOG; /* --adopt-debug: say what is happening */ +static bool TIMING; /* --timing: where startup goes */ +static double T0, T_LAST; + +/* Milliseconds since the last mark, on stderr. Cheap enough to leave in and + * the only honest way to answer "why was that slow this time". */ +static void mark(const char *what) +{ + if (!TIMING) return; + double now = now_secs(); + if (T_LAST == 0.0) T_LAST = T0; + fprintf(stderr, "swov: %-18s %6.1f ms (%6.1f total)\n", + what, (now - T_LAST) * 1000.0, (now - T0) * 1000.0); + T_LAST = now; +} @@ -1421,6 +1435,8 @@ typedef struct { char name[64]; float cores; } CpuWs; static CpuWs CPU[64]; static int NCPU; +static time_t CPU_MTIME; + static void cpu_load(void) { NCPU = 0; @@ -1437,6 +1453,7 @@ static void cpu_load(void) struct stat st; if (stat(path, &st) != 0) return; + CPU_MTIME = st.st_mtime; if (time(NULL) - st.st_mtime > 30) return; /* nobody is measuring */ FILE *f = fopen(path, "r"); @@ -4391,6 +4408,32 @@ static bool running = true; static bool dirty = true; /* a frame is only drawn when this is set */ static bool BACKDROP; /* drawn behind another program's window */ +/* swbr writes a new set of numbers every few seconds; the overview sits open + * for longer than that, so it watches the file rather than reading it once. */ +static void cpu_poll(void) +{ + if (!C.cpu) return; + + static Uint64 next; + Uint64 now = SDL_GetTicks(); + if (now < next) return; + next = now + 1000; + + char path[512]; + const char *rt = getenv("XDG_RUNTIME_DIR"); + if (rt && *rt) snprintf(path, sizeof(path), "%s/swbr-cpu", rt); + else { + const char *home = getenv("HOME"); + if (!home) return; + snprintf(path, sizeof(path), "%s/.cache/swbr-cpu", home); + } + + struct stat st; + if (stat(path, &st) != 0 || st.st_mtime == CPU_MTIME) return; + cpu_load(); + dirty = true; +} + static void reload_model(void) { /* remember what was selected so a reload does not lose the cursor */ @@ -4428,6 +4471,7 @@ static void reload_model(void) } cpu_load(); /* whatever swbr last measured */ + mark("cpu numbers"); layout(); apply_filter(); rebuild_chrome(); @@ -4536,17 +4580,56 @@ static void fontconfig_resolve(const char *family) if (!n) snprintf(fam, sizeof(fam), "sans"); } - char cmd[512]; - snprintf(cmd, sizeof(cmd), - "fc-match -f '%%{file}\n' '%s' 2>/dev/null; " - "fc-match -f '%%{file}\n' '%s:bold' 2>/dev/null", fam, fam); + /* The answer never changes between runs, and a shell plus two fc-match + * processes is the most expensive thing startup does — tens of + * milliseconds when the fontconfig cache is warm, far worse when it is + * not. Keep it next to the usage file and the whole thing becomes two + * reads. */ + char cache[512] = ""; + const char *xdg = getenv("XDG_CACHE_HOME"), *home = getenv("HOME"); + if (xdg && *xdg) snprintf(cache, sizeof(cache), "%s/swov/fonts", xdg); + else if (home && *home) snprintf(cache, sizeof(cache), "%s/.cache/swov/fonts", home); + + char l1[1024] = {0}, l2[1024] = {0}, want[160] = {0}; + snprintf(want, sizeof(want), "%s", fam); + + if (cache[0]) { /* family, then the two paths */ + FILE *c = fopen(cache, "r"); + if (c) { + char had[160] = {0}; + if (fgets(had, sizeof(had), c)) str_trim(had); + if (fgets(l1, sizeof(l1), c)) str_trim(l1); + if (fgets(l2, sizeof(l2), c)) str_trim(l2); + fclose(c); + if (strcmp(had, want) || !file_readable(l1)) l1[0] = l2[0] = 0; + } + } - FILE *p = popen(cmd, "r"); - if (!p) return; - char l1[1024] = {0}, l2[1024] = {0}; - if (fgets(l1, sizeof(l1), p)) str_trim(l1); - if (fgets(l2, sizeof(l2), p)) str_trim(l2); - pclose(p); + if (!l1[0]) { + char cmd[512]; + snprintf(cmd, sizeof(cmd), + "fc-match -f '%%{file}\n' '%s' 2>/dev/null; " + "fc-match -f '%%{file}\n' '%s:bold' 2>/dev/null", fam, fam); + + FILE *p = popen(cmd, "r"); + if (!p) return; + if (fgets(l1, sizeof(l1), p)) str_trim(l1); + if (fgets(l2, sizeof(l2), p)) str_trim(l2); + pclose(p); + + if (cache[0] && l1[0]) { + char *slash = strrchr(cache, '/'); /* mkdir -p, one level */ + if (slash) { + *slash = 0; + char *up = strrchr(cache, '/'); + if (up) { *up = 0; mkdir(cache, 0755); *up = '/'; } + mkdir(cache, 0755); + *slash = '/'; + } + FILE *c = fopen(cache, "w"); + if (c) { fprintf(c, "%s\n%s\n%s\n", want, l1, l2); fclose(c); } + } + } if (l1[0] && file_readable(l1)) FC_REGULAR = xstrdup(l1); if (l2[0] && file_readable(l2)) FC_BOLD = xstrdup(l2); @@ -4585,6 +4668,7 @@ static void load_fonts(void) bool have_paths = C.font[0] && strchr(C.font, '/') && (!C.font_bold[0] || strchr(C.font_bold, '/')); if (!have_paths) fontconfig_resolve(C.font); + mark("font lookup"); char *regular = pick_font(C.font, false); if (!regular) die("no usable font found (install fontconfig and a TTF font)"); @@ -4605,6 +4689,7 @@ static void load_fonts(void) FC_REGULAR = FC_BOLD = NULL; if (!F_BADGE || !F_LABEL || !F_TITLE || !F_HINT) die("could not open fonts: %s", SDL_GetError()); + mark("font open"); } /* ------------------------------------------------------------------ main */ @@ -4653,6 +4738,7 @@ static void usage(void) " and exit. Runs in the background; --adopt-wait keeps it\n" " in the foreground, --adopt-focus also switches there,\n" " --adopt-timeout SECS gives up after that (default 20)\n" +" --timing print how long each part of startup took, to stderr\n" " --backdrop-debug the same, narrating the conversation to stderr\n" " --backdrop draw the overview behind another window, taking no\n" " input of its own: it fades in, and reads commands on\n" @@ -5600,6 +5686,7 @@ int main(int argc, char **argv) } else if (!strcmp(a, "--adopt-focus")) adopt_focus = true; else if (!strcmp(a, "--adopt-debug")) ADOPT_LOG = true; + else if (!strcmp(a, "--timing")) TIMING = true; else if (!strcmp(a, "--no-assign")) adopt_no_assign = true; else if (!strcmp(a, "--beside") && i + 1 < argc) adopt_beside = atoi(argv[++i]); else if (!strcmp(a, "--edge") && i + 1 < argc) adopt_edge = argv[++i]; @@ -5762,8 +5849,10 @@ int main(int argc, char **argv) } } + if (TIMING) T0 = now_secs(); sway_fd = sway_connect(); if (sway_fd < 0) die("cannot reach sway (is SWAYSOCK set?)"); + mark("sway socket"); if (list_ws) { int rc = print_workspaces(); @@ -5839,6 +5928,7 @@ int main(int argc, char **argv) SDL_SetAppMetadata(APP_ID, SWOV_VERSION, "org.swov.overview"); if (!SDL_Init(SDL_INIT_VIDEO)) die("SDL_Init: %s", SDL_GetError()); + mark("SDL_Init"); if (!TTF_Init()) die("TTF_Init: %s", SDL_GetError()); SDL_Rect bounds; @@ -5853,10 +5943,12 @@ int main(int argc, char **argv) REN = SDL_CreateRenderer(WIN_HANDLE, NULL); if (!REN) die("SDL_CreateRenderer: %s", SDL_GetError()); + mark("window+renderer"); SDL_SetRenderDrawBlendMode(REN, SDL_BLENDMODE_BLEND); SDL_SetRenderVSync(REN, C.vsync ? 1 : 0); if (!create_target()) die("cannot size the window"); + mark("render target"); load_fonts(); CUR_ARROW = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); @@ -5865,6 +5957,7 @@ int main(int argc, char **argv) usage_sync(); sway_subscribe_events(); if (!model_reload()) die("could not read the sway tree"); + mark("sway tree"); cpu_load(); /* whatever swbr last measured */ select_current_workspace(); @@ -5898,6 +5991,7 @@ int main(int argc, char **argv) while (running) { SDL_Event e; if (anim_running()) dirty = true; + cpu_poll(); if (BACKDROP) { backdrop_poll();