Skip navigation

Author Archives: hrbrmstr

Don't look at me…I do what he does — just slower. #rstats avuncular • ?Resistance Fighter • Cook • Christian • [Master] Chef des Données de Sécurité @ @rapid7

Before digging into this post, I need to set some context.

Friday, May 13, 2022 was my last day at my, now, former employer of nearly seven years. I’m not mentioning the company name1 because this post is not about them2.

This post is about burnout and the importance of continuous monitoring and maintenance of you.


Occasionally, I mention3 that I’m one of those Peloton cult members. Each instructor has a pull-list of inspirational quotes that they interject in sessions4, and I’ve worked pretty hard across many decades curating mental firewall rules for such things, as words can have real power and should not be consumed lightly.

Like any firewall, some unintended packets get through, and one of Jess King’s mantras kept coming back to me recently as I was post-processing my decision to quit.

My biggest fear is waking up tomorrow and repeating today.

Many events ensued, both over the years and very recently, prior to giving notice, which was three weeks before my last day. Anyone who has built a fire by hand, by which I mean use a technique such as a bow drill vs strike a match, knows that it can take a while for the pile of kindling to finally go from docile carbon to roaring flame. For those more inclined to books5 than bivouacs, it’s also a bit like bankruptcy:

“How did you go bankrupt?” Bill asked.
“Two ways,” Mike said. “Gradually and then suddenly.”

That’s how I’d describe finally making the decision.

Personal Observability Failures

Observability is a measure of how well internal states of a system can be inferred from knowledge of its external outputs.6 I’m using that term as many folks reading this will have come from similar technical backgrounds and it has been my (heh) observation that technically inclined folks seem to have a harder time with emotional language than they do with technical language. I certainly do.

The day after officially giving notice, I went — as usual — to the DatCave to begin the day’s work after getting #4 and $SPOUSE ready for school(s). After about an hour, I looked down and noticed I wasn’t using my wrist braces.

I should probably describe why that was a Big Deal™.

For the past ~2.5 years I’ve had to wear wrist braces when doing any keyboard typing at all. I’ve had a specific RSI7 condition since high school that has, on occasion, required surgery to correct. Until this flare-up started, I had not needed any braces, or had any RSI pain, for ages8.

But, ~2.5 years ago I started to have severe pain when typing to the point where, even with braces, there were days I really couldn’t type at all. Even with braces, this bout of RSI also impacted finger coordination to the extent that I had to reconfigure text editors to not do what they usually would for certain key combinations, and craft scripts to fix some of the more common errors said lack of coordination caused. I could tell surgery could have helped this flare-up, but there’s no way I was going for elective surgery during a pandemic.

Seeing full-speed, error-free, painless typing sans-braces was a pretty emotional event. It was shortly thereafter when I realized that I had pretty much stopped reading my logs (what normal folks would might say as “checking in with myself”) ~3 years ago.

Fans of observability know that a failing complex system may continue to regularly send critical event logs, but if nothing is reading and taking action9 on those logs, then the system will just continue to degrade or fail completely over time, often in unpredictable ways.

After a bit more reflection, I realized that, at some point, I became Bill Murray10, waking up each day and just repeating the last day, at least when it came to work. I think I can safely say Jess’ (and Phil11‘s) biggest fear is now at least in my own top five.

Burnout, general stress, the Trump years, the rise of Christian nationalism, the pandemic, and the work situation all contributed to this personal, Academy Award-winning performance of Groundhog Day and I’m hoping a small peek into what I saw and what I’m doing now will help at least one other person out there.

Personal Failure Mode Effects And Mitigations

There’s a process in manufacturing called “failure mode and effects analysis”12 that can be applied to any complex system, including one’s self. It’s the structured act of reviewing as many components, assemblies, and subsystems as possible to identify potential failure modes in a complex system and their causes and effects.

Normal folks would likely just call this “self-regulation, recovery, and stress management”13,14.

My human complex system was literally injuring itself (my particular RSI is caused by ganglia sac growth; the one in my left wrist is now gone and the right wrist is reducing, both without medical intervention, ever since quitting), but rather than examine the causes, I just attributed it to “getting old”, and kept on doing the same thing every day.

I’ll have some more time for self-reflection during this week of funemployment, but I’ve been assessing the failure modes, reading new recovery and management resources, and wanted to share a bit of what I learned.

Some new resources linked-to in the footnotes, and found in annotated excerpts below, that I have found helpful in understanding and designing corrective systems for my personal failure modes are from Cornell.

  • Don’t be afraid of change: For someone who is always looking to the future and who groks “risk management”, I’m likely one of the most fundamentally risk-averse folks you’ve encountered.

    I let myself get stuck in a pretty unhealthy situation mostly due to fear of change and being surface-level comfortable. If I may show my red cult colors once again, “allow yourself the opportunity to get uncomfortable” should apply equally to work as it does to watts.

    Please do not let risk aversion and surface-level comfort keep you in a bad situation. My next adventure is bolder than any previous one, and is, in truth, a bit daunting. It is far from comfortable, and that’s O.K.

  • Take care of your physical needs: Getting a good night’s rest, eating well, and exercising are all essential to being able to feel satisfaction in life. They’re also three things that have been in scarce supply for many folks during the pandemic.

    I like to measure things, but I finally found the Apple Watch lacking in quantified self utility and dropped some coin on a Whoop band, and it was one of the better investments I’ve made. I started to double-down on working out when I learned I was going to be a pampa15, as I really want to be around to see him grow up and keep up with him. I’ve read a ton about exercise, diet, etc. over the years, but the Whoop (and Peloton + Supernatural coaches) really made me understand the importance of recovery.

    Please make daily time to check in with your mental and physical stress levels and build recovery paths into your daily routines. A good starting point is to regularly ask yourself something like “When I listen to my body, what does it need? A deep breath? Movement? Nourishment? Rest?”

  • Engage in activities that build a sense of achievement: The RSI made it nigh impossible to engage with the R and data science communities, something which I truly love doing, but now realize I was also using as a coping mechanism for the fact that a large chunk of pay-the-bills daily work was offering almost no sense of achievement16. I’m slowly getting back into engaging with the communities again, and I know for a fact that the it will be 100% on me if I do not have a daily sense of achievement at the new pay-the-bills daily workplace.

    It’d be easy for me to say “please be in a job that gives you this sense of daily achievement”, but, that would be showing my privilege. As long as you can find something outside of an achievement-challenged job to give you that sense of achievement (without falling into the similar trap I did) then that may be sufficient. The next bullet may also help for both kinds of work situations.

    You can also be less hard on yourself outside of work/communities and let yourself feel achieved for working out, taking a walk, or even just doing other things from the first bullet.

  • Changing thoughts is easier than changing feelings: Thoughts play a critical role in how we experience a situation. When you notice yourself first becoming frustrated or upset, try to evaluate what you are thinking that is causing that emotion.

    This is also known as cognitive re-framing/restructuring17. That footnote goes to a paper series, but a less-heady read is Framers, which is fundamentally about the power of mental models to make better decisions. I’d note that you cannot just “stop caring” to dig yourself out of a bad situation. You will just continue to harm yourself.

    Note that this last bullet can be super-hard for those of us who have a strong sense of “justice”, but hang in there and don’t stop working on re-framing.

FIN

I let myself get into a situation that I never should have.

Hindsight tells me that I should have made significant changes about four years ago, and I hope I can remember this lesson moving forward since there are fewer opportunities for “four year mistakes” ahead of me than there are behind me.

Burnout — which is an underlying component of above — takes years to recover from. Not minutes. Not hours. Not days. Not weeks. Not months. Years.

I’m slowly back to trying to catch up to mikefc when it comes to crazy R packages. I have more mental space available than I did a few years ago, and I’m healthier and more fit than I have been in a long time. I am nowhere near recovered, though.

If you, too, lapsed when it comes to checking in with yourself, there’s no time like the present to restart that practice. The resources I posted here may not work for you, but there are plenty of good ones out there.

If you’ve been doing a good job on self-care, make sure to reach out to others you may sense aren’t in the same place you are. You could be a catalyst for great change.


  1. I mean, you do have LinkedIn for discovering things like that 
  2. Though you’d be hard-pressed to not think some folks there only listen to Carly Simon 
  3. Usually on Twitter (b/c ofc) 
  4. Which is part of what makes it a bona fide cult 
  5. The Sun Also Rises 
  6. Observability 
  7. RSI 
  8. RSI wasn’t the only negative physical manifestation, but listing out all the things that manifest and got better isn’t truly necessary. 
  9. Software systems observability 
  10. Groundhog Day 
  11. I’m a billion years old, have seen Groundhog Day far more than a few times, and just got the joke (as I was writing this post) that Murray’s character was named “Phil”. 
  12. FMEA 
  13. Cornell: Emotional regulation [PDF] 
  14. Cornell: Stress management strategies [PDF] 
  15. Belter creole for granddad (et al) 
  16. I feel compelled to note that I was able to perform many, many work activities over the course of nearly seven years that brought a great sense of achievement. For a host of reasons, they went from a stream to a trickle. 
  17. Cognitive Restructuring 

In my M-F newsletter today I mentioned an awesome Rust-based HTML/JS/CSS minifier library that also include batteries for a few other languages.

There was no R port, so I made one using {rextendr}. The {rextendr} package makes is as easy to use Rust code in R packages as {Rcpp} does C/C++ code.

It was as simple as adding some dependencies to the Rust Cargo.toml file and then adding one Rust function to the main lib.rs file, and writing a thin wrapper function ({rextendr} can do that, too, but I wanted some default function parameters) for the shim. It took almost no time, and now you, too, can use the utility:

library(minifyhtml)

'
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
  <!-- COMMENT -->
  <style>
    * { color: black; }
  </style>
  <title>TITTLE</title>
  </head>
  <body>
    <p>
       Some text
    </p>
    <script>
      console.log("This is a console log message.");
    </script>
  </body>
</html>
' -> src

cat(minify(src))
## <html xmlns=http://www.w3.org/1999/xhtml><meta charset=UTF-8><meta content=width=device-width,initial-scale=1 name=viewport><style>* { color: black; }</style><title>TITTLE</title><body><p>Some text</p><script>console.log("This is a console log message.");</script>

FIN

I have to work out one kink (due to developing on arm64 macOS) and the utility will also be able to minify CSS and JS embedded in HTML.

You can find {minifyhtml} on GitHub.

Just a quick post to note that I’ll still be doing long-form, single-topic posts here on the blog, but I’ve also started an M-F daily Substack newsletter [free], that introduces 1-3(ish) topics per-issue on stuff I find during my daily RSS trawls.

It’s a mix of tech, Mac, linux, data science, science-science, food, and some odds-and-ends that folks seem to find interesting so far.

There are a few issues up in the archive, so you can get a feel for what it’s all aboot.

I may drop some podcasts into there as well.

I came across a neat site that uses a Golang wasm function called from javascript on the page to help you see if your GitHub public SSH keys are “safe”. What does “safe” mean? This is what the function checks for (via that site):

Recommended key sizes are as follows:

  • For the RSA algorithm at least 2048, recommended 4096
  • The DSA algorithm should not be used
  • For the ECDSA algorithm, it should be 521
  • For the ED25519, the key size should be 256 or larger

The site also provides links to standards and guides to support the need for stronger keys.

I threw together a small R package — {pubcheck} — to check local keys, keys in a character vector, and keys residing in GitHub. One function will even check the GitHub keys of all the GitHub users a given account is following:

Local file

library(pubcheck)
library(tidyverse)

check_ssh_pub_key("~/.ssh/id_rsa.pub") |> 
  mutate(key = ifelse(is.na(key), NA_character_, sprintf("%s…", substr(key, 1, 30)))) |> 
  knitr::kable()
key algo len status
ssh-rsa AAAAB3NzaC1yc2EAAAADAQ… rsa 2048 ✅ Key is safe; For the RSA algorithm at least 2048, recommended 4096

A GitHub user

check_gh_user_keys(c("hrbrmstr", "mikemahoney218")) |> 
  mutate(key = ifelse(is.na(key), NA_character_, sprintf("%s…", substr(key, 1, 30)))) |> 
  knitr::kable()
user key algo len status
hrbrmstr ssh-rsa AAAAB3NzaC1yc2EAAAADAQ… rsa 2048 ✅ Key is safe; For the RSA algorithm at least 2048, recommended 4096
hrbrmstr ssh-rsa AAAAB3NzaC1yc2EAAAADAQ… rsa 2048 ✅ Key is safe; For the RSA algorithm at least 2048, recommended 4096
hrbrmstr ssh-rsa AAAAB3NzaC1yc2EAAAADAQ… rsa 2048 ✅ Key is safe; For the RSA algorithm at least 2048, recommended 4096
mikemahoney218 ssh-rsa AAAAB3NzaC1yc2EAAAADAQ… rsa 4096 ✅ Key is safe
mikemahoney218 ssh-ed25519 AAAAC3NzaC1lZDI1NT… ed25519 256 ✅ Key is safe
mikemahoney218 ssh-ed25519 AAAAC3NzaC1lZDI1NT… ed25519 256 ✅ Key is safe
mikemahoney218 ssh-ed25519 AAAAC3NzaC1lZDI1NT… ed25519 256 ✅ Key is safe

Keys of all the users a GitHub account is following

check_gh_following("koenrh") |> 
  mutate(key = ifelse(is.na(key), NA_character_, sprintf("%s…", substr(key, 1, 30)))) |> 
  knitr::kable()
user key algo len status
framer NA NA NA NA
jurre ssh-rsa AAAAB3NzaC1yc2EAAAADAQ… rsa 2048 ✅ Key is safe; For the RSA algorithm at least 2048, recommended 4096

What’s it like out there?

I processed my followers list and had some interesting results:

library(pubcheck)
library(hrbragg)
library(tidyverse)

# this takes a while as the # of users is > 500!
res <- check_gh_following("hrbrmstr")

res |> 
  count(user) |> 
  arrange(n) |> 
  count(n, name = "n_users") |> 
  mutate(csum = cumsum(n_users)) |> 
  ggplot() +
  geom_line(
    aes(n, csum)
  ) +
  geom_point(
    aes(n, csum)
  ) + 
  scale_x_continuous(breaks = 1:21) +
  scale_y_comma() +
  labs(
    x = "# Keys In User GH", y = "# Users",
    title = "Cumulative Plot Of User/Key Counts [n=522 users]",
    subtitle = "A handful of users have >=10 keys configured in GitHub; one has 21!!"
  ) +
  theme_cs(grid="XY")

res |> 
  count(algo, len, status) |> 
  mutate(kind = ifelse(is.na(status), "No SSH keys in account", sprintf("%s:%s\n%s", algo, len, status))) |> 
  mutate(kind = fct_reorder(gsub("[;,]", "\n", kind), n, identity)) |> 
  ggplot() +
  geom_col(
    aes(n, kind),
    width = 0.65, 
    fill = "steelblue", 
    color = NA
  ) +
  scale_x_comma(position = "top") +
  labs(
    x = NULL, y = NULL,
    title = "SSH Key Summary For GH Users hrbrmstr Is Following"
  ) +
  theme_cs(grid="X") +
  theme(plot.title.position = "plot")

FIN

Whether you use the website or the R package, it’d be a good idea to check on your SSH keys at least annually.

The morning before work was super productive and there is a nigh-complete DSL for ESC/POS commands along with the ability to just print {ggplot2}/{grid} object.

I changed the package name to {escpos} since it is no longer just plot object focused, and the DSL looks a bit like this:

library(stringi)
library(hrbrthemes)
library(ggplot2)
library(escpos)

ggplot() +
  geom_point(
    data = mtcars,
    aes(wt, mpg),
    color = "red"
  ) +
  labs(
    title = "A good title"
  ) +
  theme_ipsum_es(grid="XY") -> gg

epson_ip = "HOSTNAME_OR_IP_OF_YOUR_PRINTER"

escpos(epson_ip) |>
  pos_bold("on") %>%
  pos_align("center") %>%
  pos_size("2x") %>%
  pos_underline("2dot") %>%
  pos_plaintext("This Is A Title") %>%
  pos_lf(2) |>
  pos_underline("off") %>%
  pos_size("normal") %>%
  pos_align("left") %>%
  pos_bold("off") %>%
  pos_font("b") %>%
  pos_plaintext(
    stringi::stri_rand_lipsum(1)
  ) |>
  pos_lf(2) |>
  pos_font("a") %>%
  pos_plaintext(
    paste0(capture.output(
      str(mtcars, width = 40, strict.width = "cut")
    ), collapse = "\n")
  ) |>
  pos_lf(2L) |>
  pos_plot(gg, color = TRUE) %>%
  pos_lf(2L) |>
  pos_font("c") %>%
  pos_plaintext(
    stringi::stri_rand_lipsum(1, start_lipsum = FALSE)
  ) |>
  pos_lf(3) |>
  pos_cut() %>%
  pos_print()

full capabilities ESC/POS printing

FIN

I still need to make a more generic options “setter” (i.e. so one can set multiple modes in one function call), and I think supporting some kind of markdown/HTML subset to make it easier just to specify that without using the full DSL would be helpful. More updates over the coming weeks!

At the end of March, I caught a fleeting tweet that showcased an Epson thermal receipt printer generating a new “ticket” whenever a new GitHub issue was filed on a repository. @aschmelyun documents it well in this blog post. It’s a pretty cool hack, self-contained on a Pi Zero.

Andrew’s project birthed an idea: could I write an R package that will let me plot {ggplot2}/{grid} objects to it? The form factor of the receipt printer is tiny (~280 “pixels” wide), but the near infinite length of the paper means one can play with some data visualizations that cannot be done in other formats (and it would be cool to be able to play with other content to print to it in and outside of R).

One of the features that makes Andrew’s hack extra cool is that he used an Epson receipt printer model that was USB connected. I don’t see the need to dedicate and extra piece of plastic, metal, and silicon to manage the printing experience, especially since I already have a big linux server where I run personal, large scale data science jobs. I ended up getting a used (lots of restaurants close down each week) Epson TM-T88V off of eBay since it has Ethernet and is supports ESC/POS commands.

After unpacking it, I needed to get it on the local network. There are many guides out there for this, but this one sums up the process pretty well:

  • Plug the printer in and reset it
  • Hook up a system directly to it (Ethernet to Ethernet)
  • Configure your system to use the Epson default IP addressing scheme
  • Access the web setup page
  • Configure it to work on your network
  • Disconnect and restart the printer

To make sure everything worked, I grabbed one of the (weirdly) many projects on GitHub that provided a means for formatting graphics files to an ESC/POS compatible raster bitmap and processed a simple R-generated png to it, then used netcat to shunt the binary blob over to the printer on the default port of 9100.

I did some initial experiments with {magick}, pulling the graphics bits out of generated plots and then wrapping some R code around doing the conversion. It was clunky and tedious, and I knew there had to be a better way, so I hunted for some C/C++, Rust, or Go code that already did the conversion and found png2escpos by The Working Group. However, I’ve switched to png2pos by Petr Kutalek as the dithering it does won’t require the R user to produce only black-and-white plots for them to look good.

I thought about implementing a graphics device to support any R graphics output, but there are enough methods to convert a base R plot to a grid/grob object that I decided to mimic the functionality of ggsave() and make a ggpos() function. The comment annotations in the code snippet below walk you through the extremely basic process:

ggpos <- function(plot = ggplot2::last_plot(),
                  host_pos,
                  port = 9100L,
                  scale = 2,
                  width = 280,
                  height = 280,
                  units = "px",
                  dpi = 144,
                  bg = "white",
                  ...) {

  # we generate a png file using ggsave()

  png_file <- tempfile(fileext = ".png")

  ggplot2::ggsave(
    filename = png_file,
    plot = plot,
    scale = scale,
    width = width,
    height = height,
    units = units,
    dpi = dpi,
    bg = bg,
    ...
  )

  # we call an internal C function to convert the generated png file to an ESC/POS raster bitmap file

  res <- png_to_raster(png_file)

  if (res != "") { # if the conversion ended up generating a new file

    # read in the raw bytes

    escpos_raster <- stringi::stri_read_raw(res)

    # open up a binary socket to the printer 

    socketConnection(
      host = host_pos,
      port = port,
      open = "a+b"
    ) -> con

    on.exit(close(con))

    # shunt all the bytes over to it

    writeBin(
      object = escpos_raster,
      con = con,
      useBytes = TRUE
    )

  }

  invisible(res)

}

The only work I needed to do on the original C code was to have it output directly to a file instead of stdout.

Now, plotting to the printer is as straightforward as:

library(ggplot2)
library(escpos)

ggplot(mtcars) +
  geom_point(
    aes(wt, mpg)
  ) +
  labs(
    title = "Test of {ggpos}"
  ) +
  theme_ipsum_es(grid="XY") +
  theme(
    panel.grid.major.x = element_line(color = "black"),
    panel.grid.major.y = element_line(color = "black")
  ) -> gg

ggpos(gg, host_pos = HOSTNAME_OR_IP_ADDRESS_OF_YOUR_PRINTER)

That code produces this output (I’m still getting the hang of ripping the spooled paper off this thing):

ggplot receipt

This is the whole thing in action:

One of the 2022 #30DayChartChallenge topics was “part-to-whole”, so I rejiggered my treemap entry into a very long plot that would make CVS cashiers feel quite inferior.

You can find {escpos} over on GitHub.

FIN

One big caveat for this is that these printers have a tiny memory buffer, so very long, complex plots aren’t going to work out of the box. I had to break up my faceted heatmaps info individual ones and shunt them over one-by-one.

I’ll be switching over the the new C library soon, and adding a small DSL to handle text formatting and printing from R (the device has 2 fonts and almost no styles). I’ve even threatened to make a ShinyPOS application, but we’ll see how the motivation for that goes.

Kick the tyres and let me know if you end up using the package (+ share your creation to the 🌎).

RStudio’s macOS Electron build is coming along quite nicely and is blazing fast on Apple Silicon.

I like to install the dailies, well, daily!; and, of late, RStudio and Quarto are joined at the hip. As a result, I regularly found myself having to manually update Quarto CLI right after updating RStudio, so I made a small zsh script that will use the new RStudio dailies JSON API to grab the latest daily, and the GitHub JSON API to grab the latest Quarto release, then install them both.

Caveats and limitations are in the repo.

via Zack Whittaker on Twitter…

Download Tor Browser: http://torproject.org/download

Open this URL in Tor to access BBC (🇺🇦): https://www.bbcweb3hytmzhn5d532owbu6oqadra5z3ar726vq5kgwwn6aucdccrad.onion/ukrainian

Open this URL in Tor to access BBC (🇷🇺): https://www.bbcweb3hytmzhn5d532owbu6oqadra5z3ar726vq5kgwwn6aucdccrad.onion/russian