展開所有 GitHub comment 的 Bookmarklet

看到 Eric Meyer 弄了一個可以展開 GitHub comment 的 bookmarklet:「Bookmarklet: Load All GitHub Comments」。

分析他的程式碼,稍微手動排一下,可以看出來邏輯蠻簡單的,就是去找出對應的 button,然後模擬按下去的 event:

javascript:function start() {
  let buttons = document.querySelectorAll('button');
  let loaders = [];
  for (let i = 0; i < buttons.length; i += 1) {
    if (buttons[i].textContent.trim() == 'Load more%E2%80%A6') {
      loaders.push(buttons[i]);
      buttons[i].dispatchEvent(new MouseEvent('click', {
        view: window,
        bubbles: false
      }))
    }
  }
  if (loaders.length > 0) {
    setTimeout(start, 5000)
  }
}
setTimeout(start, 500);
void(20240130);

意外可以看到一些應該是作者以前寫習慣的寫法 (畢竟 Eric Meyer 這個名字二十年前就聽過了?),現在 for 拿來當 iteration 應該會用 of 的語法了,另外是 letconst 的差異...

還好這邊還是用 querySelectorAll(),而不是直接看到 getElementsByTagName(),不然就更有考古感了...

拿「Quadratic time internal base conversions #90716」這個測了一下還行,不過我不是那麼常用到,大概不會掛到 bookmark bar 上面...

作者有提到考慮過寫成 userscript,不過看起來是懶 XD

t4g 的 CPU credit 被吃完了

這個站 blog.gslin.org 掛了三個多小時:

先連機器 SSH 看起來是正常的,但習慣性的 w 看一下情況發現 CPU load 有 6.x,用 top 看一下就看到幾隻 php82-fpm 跑滿 CPU,心裡大概有底是被砍站了...

先把 nginx 停下來,瞄了一下 /var/log/nginx 下面的 log 就知道是 ClaudeBot 造成的,看起來都是從 AWSus-east-1 機器打過來的。

然後翻一下 log 看看什麼時候開始打的,先看 log 已經被 gzip 起來的這些:

$ echo /var/log/nginx/blog.gslin.org_ssl-access.log.{?,??}.gz | xargs -n1 | xargs -n1 -I% sh -c "echo %; zgrep ClaudeBot % | wc"
/var/log/nginx/blog.gslin.org_ssl-access.log.2.gz
  13031  169403 1986719
/var/log/nginx/blog.gslin.org_ssl-access.log.3.gz
    459    5967   85350
/var/log/nginx/blog.gslin.org_ssl-access.log.4.gz
  14533  188929 2219819
/var/log/nginx/blog.gslin.org_ssl-access.log.5.gz
   6502   84526 1026178
/var/log/nginx/blog.gslin.org_ssl-access.log.6.gz
  32483  422279 4905919
/var/log/nginx/blog.gslin.org_ssl-access.log.7.gz
  21304  276952 3221877
/var/log/nginx/blog.gslin.org_ssl-access.log.8.gz
   7921  102973 1199356
/var/log/nginx/blog.gslin.org_ssl-access.log.9.gz
      0       0       0
/var/log/nginx/blog.gslin.org_ssl-access.log.10.gz
      0       0       0
/var/log/nginx/blog.gslin.org_ssl-access.log.11.gz
      0       0       0
/var/log/nginx/blog.gslin.org_ssl-access.log.12.gz
      0       0       0
/var/log/nginx/blog.gslin.org_ssl-access.log.13.gz
      0       0       0
/var/log/nginx/blog.gslin.org_ssl-access.log.14.gz
      0       0       0

看起來是從 blog.gslin.org_ssl-access.log.8.gz 這邊開始的,大概是 1/25 開始 (機器上面是 UTC 時間):

-rw-r----- 1 www-data adm   1894325 Jan 26 00:00 /var/log/nginx/blog.gslin.org_ssl-access.log.8.gz

然後再來看一下最近的 log,看起來是這兩天打的特別重,到五萬多:

$ echo /var/log/nginx/blog.gslin.org_ssl-access.log{,.?} | xargs -n1 | xargs -n1 -I% sh -c "echo %; grep ClaudeBot % | wc"
/var/log/nginx/blog.gslin.org_ssl-access.log
  29436  382668 4387703
/var/log/nginx/blog.gslin.org_ssl-access.log.1
  51712  672256 7852345

拉了 AWS 的圖來看跟預期的差不多:

機器是 t4g.micro 而且沒開 burstable,先前差不多都是略低於 10% 的線在跑,剛好利用 CPU credit 的概念,這幾天看起來就是被打而跑上去。

好像該補一下 alarm,丟到我自己的 Slack 以及 Pushover...

AWS ALB 支援 TLS 1.3?

AWSALB 宣佈支援 TLS 1.3:「Application Load Balancer now supports TLS 1.3」。

本來以為早就支援了,翻了一下發現應該是太多產品線的關係搞混了:

ALB 則是到了 2023 才支援 TLS 1.3...

NLB 接 ALB?

AWS API Changes 上看到這個修改:「2021/09/27 - Elastic Load Balancing - 3 updated api methods」。

說明是這樣:

Adds new ALB-type target group to facilitate forwarding traffic from NLB to ALB

所以是 NLB 可以接到 ALB 嗎,這看起來讓彈性變大不少...

GTA Online 釋出官方修正,大幅改善啟動效能

看到「GTA Online load time fix released, shaves off actual minutes of waiting for some」這邊的消息,先前在「GTA 的啟動讀取效能問題」這邊提到 GTA Online 啟動速度很慢的問題,官方正式推出修正版本了:「GTAV Title Update 1.53 Notes (PS4 / Xbox One / PC)」。

抓了一些在 Reddit 的討論「Loading Times Have FINALLY been patched - Discussion Thread」。

這則降的比率與當時 workaround 的修正差不多:

Insane. GTA menu -> GTA: Online.

Dropped from 7 minutes to 1:57

i7-2600k,GTX1070,16GB RAM and the game is on HDD.

這個就有點誇張了,這是 90% 吧?

Dropped from 5-8 minutes to 35 seconds

這個差不多 70%~80%:

Loading time 2m 20s for online directly from steam. Before it was like 8-10 minutes for me. Damn

Edit: 50s for story mode. 35s from story mode to online. So it seems it's still faster to load into online from story mode.

這個也差不多 70%:

From 4-5 minutes to 1 a minute and 22 seconds. Y e s p l e a s e

然後 PS4 的版本原來也受到一樣的影響?

Currently tested on PS4 , from main menu to online : 3min 45 sec From story mode to online: 1min 20sec (😩 i can't tell for sure )

整體看起來是正面的,畢竟大家等這個問題等超久了... 另外也可以看出來當初的 workaround patch 其實相當精準的把問題都解掉了,官方的修正並沒有快更多。

來繼續關注 libc 那邊的問題...

GTA 的啟動讀取效能問題

這件事情也已經過了一個禮拜,來整理一下發生什麼事情...

起因是 GTA Online 的遊戲開啟速度很慢,而有人一路 reverse engineering 找出問題並且解決:「How I cut GTA Online loading times by 70%」,對應的 Hacker News 討論有提到其他有趣的事情也可以看看:「How I cut GTA Online loading times by 70% (nee.lv)」。

作者的電腦不算太差,但光開啟 GTA Online 就需要六分鐘,網路上甚至有辦投票蒐集大家的等待時間,發現也有很多人反應類似的問題:

接下來就開始 reverse engineering 了,先觀察各種狀態後發現是卡在 CPU,而不是網路或 Disk I/O,然後就拿出 Luke Stackwalker 這個工具 profiling,不過因為沒有 debug symbol 幫忙 group,所以只能人工判斷後,可以看到兩個問題:

第一個問題發現效能是卡在 strlen(),而 call stack 可以看出來是從 sscanf() 一路打進去的:

反追發現是在處理 10MB 的 JSON 檔造成的,裡面 sscanf() 因為拉出 strlen(),於是就造成把整個 10MB 的 JSON 掃過很多次 (一開始是 10MB,掃到後面會愈來愈少,平均下來應該是 5MB):

第二個問題產生的時間會在第一個問題跑完後,另外看問題的性質,應該跟第一個 JSON 處理有關,他會把 JSON 處理過的資料丟進 array,每個 entry 長這樣:

struct {
    uint64_t *hash;
    item_t   *item;
} entry;

丟進 array 是 OK 的,但問題在於他需要判斷 entry 是否重複,卻沒有用 hash 或是 tree 的結構,而這邊大約有 63k 筆資料,用 array 實做就產生了 O(n^2) 的演算法:

But before it’s stored? It checks the entire array, one by one, comparing the hash of the item to see if it’s in the list or not. With ~63k entries that’s (n^2+n)/2 = (63000^2+63000)/2 = 1984531500 checks if my math is right. Most of them useless. You have unique hashes why not use a hash map.

作者在 PoC 的章節裡面描述他怎麼解這兩個問題。

第一個問題比較好的解法是修正 JSON Parser,但這太複雜,所以他用 workaround 解:把 strlen() 包起來,針對長字串加上一層 cache:

  • hook strlen
  • wait for a long string
  • “cache” the start and length of it
  • if it’s called again within the string’s range, return cached value

而第二個問題他直接把檢查是否有重複的跳過,因為資料本身不重複:

And as for the hash-array problem, it’s more straightforward - just skip the duplicate checks entirely and insert the items directly since we know the values are unique.

整個開啟的速度從六分鐘降到一分五十秒,還是偏慢,但算是大幅緩解的 GTA Online 啟動速度的問題了。

不過故事到這邊還沒結束,有人一路去挖,發現其實 sscanf() 的效能地雷已經不是第一次了:YAML 的 Parser 也中過一樣的問題:「Parsing can become accidentally quadratic because of sscanf」,這篇也一樣上了 Hacker News:「Parsing can become accidentally quadratic because of sscanf (github.com/biojppm)」。

然後這又帶出了六年前在 StackOverflow 上就有人問過這個問題:「Why is glibc's sscanf vastly slower than fscanf on Linux?」。

另外也有人整理出來,應該是大家把同樣的演算法拿來實做:

JdeBP 3 days ago

I found this while making a collection of what C implementation does what at https://news.ycombinator.com/item?id=26298300.

There are two basic implementation strategies. The BSD (FreeBSD and OpenBSD and more than likely NetBSD too), Microsoft, GNU, and MUSL C libraries use one, and suffer from this; whereas the OpenWatcom, P.J. Plauger, Tru64 Unix, and my standard C libraries use another, and do not.

The 2002 report in the comp.lang.c Usenet newsgroup (listed in that discussion) is the earliest that I've found so far.

後續的更新動作可以再追一下進度 (包括 GTA Online 與各家的 libc)。

ALB 支援 Sticky Session

又是一個以為很久前就已經支援,但實際上沒支援的功能...

ALB 支援使用 cookie 實現 sticky session 功能:「Application Load Balancer now supports Application Cookie Stickiness」。

使用者的 session 通常會使用 cookie 記錄,而如果有多台 server 提供服務時,session 裡的資訊就需要找一個 shared session storage 放,以確保使用者在連到不同的 server 時都還是可以讀到對應的 session,比較傳統的方案就是直接把 session 塞進資料庫,後來發展出 memcached 或是 Redis 可以用。

但有些買來的軟體並沒有考慮到這點 (常常都是內部系統),導致前面放 load balancer 時,必須想個辦法記錄使用者使用後端的哪台機器,這樣就可以在後端不支援 shared session storage 的情況下,還是可以讓應用正常運作。

透過 cookie 實做的 sticky session 算是蠻常見的作法,只是以為早就有了...

Load Impact 的 k6 網站壓測軟體

這幾天在 Hacker News 上看到 Load Impact 推出的 k6 壓測程式,結合了 Golang 的執行效率與 JavaScript 的操作語法,讓使用者可以很簡單的進行壓力測試,在 Hacker News 上也有蠻正向的反應:「K6: Like unit testing, for performance (github.com/loadimpact)」,我唯一會在意的應該是 AGPLv3 的部份...

先看了一下資訊,看起來「Load Impact」是公司名稱,「LoadImpact」則是產品名稱,然後現在要改名變成「k6」與「k6 Cloud」:

Load Impact is now k6

Due to the success and rapid growth of the k6 open source load testing tool we decided to rebrand the LoadImpact product as k6 Cloud!

k6 裡面設計了 VU (Virtual User) 的概念,如同字面上的意義,VU 是虛擬的使用者,就技術上來說,每個 VU 都是在獨立的 JavaScript runtime 裡跑:

Each virtual user (VU) executes your script in a completely separate JavaScript runtime, parallel to all of the other running VUs.

然後他們居然把 JavaScript 裡面最「經典」的 async 架構給拔了,所以就不需要一堆 callback & promise 架構,用起來就爽很多:

For simplicity, unlike many other JavaScript runtimes, a lot of the operations in k6 are synchronous. That means that, for example, the let response = http.get("https://test-api.k6.io/") call from the Running k6 example script will block the VU execution until the HTTP request is completed, save the response information in the response variable and only then continue executing the rest of the script - no callbacks and promises needed.

翻了一下 Hacker News 上的討論與程式碼,看起來 JavaScript runtime 這部份是用 Golang 寫的 goja

文件裡面給了不少範例,像是在「Running k6」這邊有直接給出怎麼壓測,10 個 VU 跑 30 秒:

k6 run --vus 10 --duration 30s script.js

另外在 repository 裡面,「samples」這個目錄下有不少範例,可以直接先看過一次從裡面學到不少功能,之後再回去翻一次 manual,應該就會更熟悉...

隨便測了一下還蠻容易上手的,加上有 apt repository 可以直接納入系統管理,看起來應該會放著跑,之後找機會用看看,也許打 API 之類的...

AWS 的 ALB 可以用最少未處理連線數分配了

AWSALB 總算提供「最少連線數」的功能了:「Application Load Balancer now supports Least Outstanding Requests algorithm for load balancing requests」。

先前只有 Round robin (RR),現在支援 Least outstanding requests (LOR) 後可以往比較少的塞了,這個方法在大多數的應用上比 RR 合理多了,現有的 ALB 應該都可以考慮換過去...

補功能不算快,但有種已知用火的感覺 XD

延遲載入 CSS 的方式

在「Simpler way to load CSS asynchronously」這邊看到的技巧,利用了 onload<noscript> 的方式,達成延遲載入 CSS 的效果:

<link rel="stylesheet" href="/path/to/my.css"
      media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="/path/to/my.css"></noscript>

對於非必要的 CSS 可以用這樣的方式加強,看起來頗不賴... 這邊提到的方式,作者是引用「Smashing Newsletter: Issue #234」,不過查了一下發現 2015 的時候有人在 StackOverflow 上回答過:「How to load CSS Asynchronously」,不確定有沒有更早的資料...

另外一個比較新的語法是使用 rel="preload",但支援度就不太好了,需要靠 polyfill 之類的方式補上 (於是又多了一些東西要 load),反而不如前面提到的方式來的簡單。