追 MediaWiki 頁面很慢的問題

我自己有個 wiki 拿來堆一些資料,前陣子覺得 Live 這頁很慢,實際測試發現要跑五秒多,想說到底是什麼鬼...

先從 MediaWiki 的各種 optimization 設定開始看,像是 Manual:Cache 這邊提到的東西,一路看下來看起來都已經設好最佳化了。

只好拿 profiling 工具來翻,在 PHP 上面用的是 Xdebug,裝了以後希望是特殊的情況才跑 profiling,所以參考了「Profiling」這邊的說明,在 php.ini 裡面這樣設定:

xdebug.mode=profile
xdebug.start_with_request=trigger
xdebug.trigger_value=StartProfileForMe

接著依照說明,你要找個地方把這個值傳進去:

Xdebug's profiler will only start when either the environment variable XDEBUG_TRIGGER is set to StartProfileForMe, the GET or POST variable XDEBUG_TRIGGER is set to StartProfileForMe, or when the cookie XDEBUG_TRIGGER has the value StartProfileForMe.

我是在網址列上直接用 ?XDEBUG_TRIGGER=StartProfileForMe 觸發,預設值會把 profiling 結果丟到 /tmp 下面,接下來就是把 callgrind 檔案視覺化了。

首先是把 xdebug 的 callgrind 格式輸出檔案轉成 DOT 格式,這邊參考「Interpreting callgrind data」的說明,先裝 gprof2dot,這個軟體在 PyPI 上有,所以可以用 pipx 裝,接著把 callgrind 轉完後再轉成 PNG 檔案。

我把圖放到最後面 (點開可以看大圖),第一張是 Live 這頁的 profiling,第二張是 Hosting 這頁的。

我注意到在 Live 這頁 php::mysqli 這邊被呼叫了幾千次,這邊就算是本地端的資料庫,幾千次的 latency 累積起來還是很驚人,畢竟是跨 process 之間的溝通,而且已知是 ping-pong 要等待的情況。

接著去翻是什麼造成的,看起來跟 selectRow 有關,但線索到這邊就斷掉了,上面的 User->load 看起來並沒有 loop 導致大量呼叫 selectRow

但至少有個方向,從網路上沒什麼討論看起來,應該不會是本體的效能問題,而是 extension 造成的,從這個角度開始追與帳號 & 權限有關的 extension,第一發懷疑是 Extension:AccessControl 造成的,結果果然沒錯... 拔掉後頁面的速度就從 5.1 秒左右降到 1.5 秒左右了。

沒有實際追程式碼,但猜測是 wiki 裡面用的 Template 造成 AccessControl 都會去檢查權限,加上這邊沒有上 cache,造成頁面上 Template 一多效能很差...

最後,這是 Live 頁的 profiling graph:

這是 Hosting 頁的 profiling graph:

Keyboard Event:在網頁上自定義快速鍵

先前寫了一堆 userscript 針對不同的網站加上快速鍵 (像是翻下一頁或是上一頁):

然後發現太多重複的事情了,就寫成 Chrome 的 extension 了:「Keyboard Event」,程式碼在 GitHub 上有放:「gslin/keyboard-event」,不信任一致性的話也可以用 Chrome extension source viewer 直接看 chrome web store 上面放的版本。

這個 extension 的想法是設定 hostname、path 以及按鍵,接著設定按鍵按下後會觸發的 DOM element (用 CSS selector 選) 與 DOM event (像是 mouseover 這樣的 event 字串)。

而其中 click event 常常不會動 (對應實際的呼叫是 element.dispatchEvent(new Event('click'))),在 debug 時發現需要用 click() (對應實際的呼叫是 element.click()),所以有特別接受 click() 這個字串。

這個舉個例子,我有一組設定是:

www.instagram.com
.*
<
div[role="dialog"] button[aria-label="Go back"]
click()

www.instagram.com
.*
>
div[role="dialog"] button[aria-label="Next"]
click()

在瀏覽器上面看 Instagram 的照片時可以用 <> 快速切換照片。

另外設定的部分支援 chrome.storage 的 browser sync,對於有多瀏覽器時會自動同步設定檔,不用每一台都設定一次...

這算是第一次寫 Manifest V3 的 extension (先前都是 V2),本來想用 Vue.js 處理設定頁 options.html,但遇到了 CSP 的問題,在 Manifest V3 上面無法設定 unsafe-eval,於是 Vue.js 只能用 runtime 版本,喪失了 template 好用的地方...

後來決定回頭用 jQuery + jQuery UI (用 sortable),然後自己處理一堆 data 更新時頁面的變化,一整個 2010 年老人味都出來了...

但這樣至少會動,先跑一陣子看看...

Wikimedia 新計畫:WikiFunctions

Wikimedia 的新計畫 WikiFunctions:「Introducing Wikifunctions: first Wikimedia project to launch in a decade creates new forms of knowledge」。

官方的說明中提到 WikiFunctions 是提供給 Wikimedia 其他的計畫用的程式碼:

Wikifunctions is a Wikimedia project for everyone to collaboratively create and maintain a library of code functions to support the Wikimedia projects and beyond, in the world's natural and programming languages.

這樣有點抽象,看「Wikifunctions:Catalogue」這邊的範例會比較具體一點,可以看到都是實作很基本的功能,像是「Function: To Uppercase」。

Hacker News 上也有討論:「Wikifunctions (wikimediafoundation.org)」,在 id=38549081 這邊有人比較清楚的說明了要解決的問題:

A lot of Wikipedia sites have scripts embedded in the wikitext which automatically generate or transform information on a page, e.g. automatically performing unit conversions to generate text like "I would walk 500 miles (804.67 km)", performing date math to automatically generate and update a person's age based on their birthdate, or querying structured data from Wikidata [1] to display in an infobox. One example of these scripts is the {{convert}} [2] template on the English Wikipedia.

Initially, these scripts were written in MediaWiki template logic [3], and were maintained individually on each wiki. This quickly proved unmaintainable, and some of those scripts were rewritten in Lua using the Scribunto extension [4], but these were still per-wiki, and there were frequently issues where different wikis would copy scripts from each other and introduce their own incompatible features.

The WikiFunctions project is an attempt to centralize development of these scripts, much like how Wikimedia Commons [5] centralizes hosting of freely licensed images and other media.

MediaWiki 本身的 template engine 還是有極限,所以很多的需求會希望使用程式語言開發,一方面是比較好維護,另外一方面是效率也比 template engine 硬幹來的好。

先前大家在 MediaWiki 上比較常用的方案是透過 Extension:ScribuntoLua script,而這個 extension 後來變得太重要 (好用),直接包進 MediaWiki 了。

拿個例子來說,常見的引用功能 Template:Citation 裡面直接是透過模組實作:

<includeonly>{{#invoke:citation/CS1|citation
|CitationClass=citation
}}</includeonly><noinclude>
{{Documentation}}
</noinclude>

而這邊的 #invoke 就是去使用「Module:Citation/CS1」這邊的 Lua 程式。

目前的架構上面,每個語言的 Wikipedia 都需要使用 Lua 維護自己的 Module:*,而 WikiFunctions 計畫則是希望能夠整合起來,算是重新打造基礎建設的工程。

目前看了一些 WikiFunctions 的實作,主要是以 PythonJavaScript 為主。

這樣看起來,技術上是蠻有機會讓 Scribunto 退役,但實際上要不要這樣推還沒查到討論,在 Wikifunctions:FAQ 這邊則是沒提到 Scribunto,不確定是不是想雙軌跑...

用 Little Rat 看哪些 extension 在後面亂連線

還在日本的時候在 Hacker News 上看到「Show HN: Little Rat – Chrome extension monitors network calls of all extensions (github.com/dnakov)」這篇,在介紹「little-rat」這個專案,可以看 extension 做了那些連線。

放上 Chrome Web Store 的版本比較陽春,只能擋而不能看,因為 declarativeNetRequest.onRuleMatchedDebug 不能上:

The reason is that the extension uses the declarativeNetRequest.onRuleMatchedDebug API which is not available for publishing in the Chrome Web Store.

比較完整的功能還是需要自己 git clone 下來後裝,缺點就是要每一台自己裝,另外也要自己更新重跑起來。

裝好後跑一陣子讓他記錄,可以看到有些連線是預料中的,像是 uBlock Origin 會需要定時更新 rule,但也意外的看到一些有問題的 extension 了...

SHA-256 的 Length extension attack

Hacker News 上看到「Breaking SHA256: length extension attacks in practice (kerkour.com)」,在講不當使用 SHA-256 會導致 Length extension attack 類的安全漏洞,主要是因為 MD5SHA-1 以及 SHA-2 類的 hash function 最後生出 hash 值時會暴露出 hash function 的內部狀態而導致的問題。

這邊講的不當使用是指你沒有使用標準的 MAC,而是自己用字串組合實作造成的問題,通常是 S = H(secret || message) 這樣的形式,這邊的 || 是指字串相接。

拿 MD5 為例子,在維基百科上面可以看到 MD5 演算法對應的 pseudo code,最後輸出的部分可以看到是把 a0a1a2a3 這四個 32-bit variable 接起來,也就是把內部的狀態丟出來了:

// Process the message in successive 512-bit chunks:
for each 512-bit chunk of padded message do
    // ...

    // Add this chunk's hash to result so far:
    a0 := a0 + A
    b0 := b0 + B
    c0 := c0 + C
    d0 := d0 + D
end for

var char digest[16] := a0 append b0 append c0 append d0 // (Output is in little-endian)

於是你在可以反推 padding 的結構之後 (會需要知道 secret 的長度),就可以往後接東西繼續算下去,這就是被稱作 length extension attack。

本來只有 S = H(secret || message),你在不知道 secret 的情況下就可以疊字串到後面而且算出對應的 hash 值,變成 S' = H(secret || message || evildata)

維基百科給的例子也示範了怎麼「用」,這是原始的資料以及 server 端簽出來的 hash 值:

Original Data: count=10&lat=37.351&user_id=1&long=-119.827&waffle=eggo
Original Signature: 6d5f807e23db210bc254a28be2d6759a0f5f5d99

於是我們想要蓋 waffle 參數,就變成:

Desired New Data: count=10&lat=37.351&user_id=1&long=-119.827&waffle=eggo&waffle=liege

攻擊者則可以不斷的嘗試,去猜測 padding 的結構,把計算出來對應的 hash 值丟到 server 看反應,直到看到 200 OK 的回應:

New Data: count=10&lat=37.351&user_id=1&long=-119.827&waffle=eggo\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x28&waffle=liege
New Signature: 0e41270260895979317fff3898ab85668953aaa2

如同前面提到的,這是 hash function 在最後把內部狀態直接暴露出來造成的問題,在 MD5、SHA-1、SHA-2 (SHA-256、SHA-384、SHA-512) 都有類似的問題,而比較新的 hash function 在設計時就已經有考慮到了,不會出現這個問題,像是 SHA-3

另外一方面,不要自己發明演算法,使用標準的 MAC 演算法通常是比較好的選擇。這邊用的比較廣泛的應該就是 HMAC,超過 25 年了。

結論是 SHA-256 還是堪用,儘量拿現成的演算法套,不要自己搞。

FBI 建議用擋廣告軟體降低瀏覽時的風險

在「Even the FBI says you should use an ad blocker」這邊看到的新聞,FBI 的公告則是在「Cyber Criminals Impersonating Brands Using Search Engine Advertisement Services to Defraud Users」這邊可以看到。

起因是有很多網路犯罪行為會透過購買廣告,在搜尋引擎上曝光誘導使用者點擊:

Cyber criminals purchase advertisements that appear within internet search results using a domain that is similar to an actual business or service. When a user searches for that business or service, these advertisements appear at the very top of search results with minimum distinction between an advertisement and an actual search result. These advertisements link to a webpage that looks identical to the impersonated business’s official webpage.

其中一種方式是,使用者輸入關鍵字想要下載某些特定的軟體,這時候網路犯罪者就會透過下廣告的方式,誘導使用者到假的網站下載有後門木馬的軟體:

In instances where a user is searching for a program to download, the fraudulent webpage has a link to download software that is actually malware. The download page looks legitimate and the download itself is named after the program the user intended to download.

這個方式讓我想到之前北韓政府對 PuTTY 的攻擊:「Trojanized versions of PuTTY utility being used to spread backdoor」。

而 FBI 建議個人的保護方式包括了 ad blocking extension,這算是減少被攻擊的管道:

Use an ad blocking extension when performing internet searches. Most internet browsers allow a user to add extensions, including extensions that block advertisements. These ad blockers can be turned on and off within a browser to permit advertisements on certain websites while blocking advertisements on others.

然後建議擋廣告軟體就是用 uBlock Origin,無論是 Chromium 系列的瀏覽器 (包括 Google Chrome),或是 Firefox 都有支援。

Mozilla 推出在本地端直接翻譯的 Firefox Translations

Mozilla 推出了「Firefox Translations」這個 Firefox 上的套件。

主打的就是 offline 這件事情,保有隱私性:

Firefox Translations provides automated translation of web content. Unlike cloud-based alternatives, translation is done locally, on the client-side, so that the text being translated does not leave your machine.

Hacker News 上的討論「Firefox Translations: Translate websites in your browser without using the cloud (addons.mozilla.org)」可以看到有些人有提到效果,雖然沒有像雲端服務的準確,但算是可用:

I've just installed it, and I'm impressed so far. I've only run it against some sample German Wikipedia articles (https://de.wikipedia.org/wiki/Clan_of_Xymox), but it produces surprisingly readable text. I also particularly like the "highlight potential errors" option to flag stuff that even the translation service thinks might be a bit off.

It's not nearly as speedy as Google Translate, but I'll take that happily if it means keeping it local.

從頁面上列出的支援語言可以看出還是以歐美用到的語系為主,然後下方也有說明這個專案是包括其他計畫的贊助累積出來的:

Firefox Translations was developed with The Bergamot Project Consortium, coordinated by the University of Edinburgh with partners Charles University in Prague, the University of Sheffield, University of Tartu, and Mozilla. This project has received funding from the European Union’s Horizon 2020 research and innovation programme under grant agreement No 825303.

不過比較好奇的是在頁面上有提到 CPU 需要 SSE4.1 能力... 這樣就有兩個問題了,第一個是 browser extension 可以直接跑 SSE4.1 指令集?另外一個疑問就是,AppleARM 架構就無法支援嗎 (應該也有類似的加速指令集),現在是 x86 限定?

A CPU that supports SSE4.1 extensions is required for this addon to function properly. If it doesn't, an error will be displayed when the translation is being started.

就算現在限制很多,看起來還是個很有前途的計畫,也許有機會移植到其他瀏覽器上?

iOS Safari 擋 "Open in App" 的付費套件 Banish

在「New iOS App Blocks Those Annoying 'Open in App' Pop-Ups in Safari」這邊看到 John Gruber 的介紹文章「Banish: New Safari Extension to Block 'Open in App' Dickpanels」,裡面提到的 extension 在「Banish for Safari」這邊,一次性的費用,在台灣是 NT$70。

裡面的 screenshot 給了還蠻清楚的說明:

設定上面因為會牽扯到 privacy 的關係,會有點麻煩,需要開好幾個地方。

順道一提,桌面上的話可以透過 Annoyances 系列的 list 在 uBlock Origin 上擋。

SQLite 的 zstd extension

看到「sqlite-zstd」這個實驗性質的專案,可以針對 SQLite 的 row-level 層壓縮:

Extension for sqlite that provides transparent dictionary-based row-level compression for sqlite. This basically allows you to compress entries in a sqlite database almost as well as if you were compressing the whole DB file, but while retaining random access.

看起來在空間上有很不錯的成果:

作者在他自己的 blog 上面有給了一篇比較完整的說明,除了空間上的優勢以外,還包含了效能上的分析:「sqlite-zstd: Transparent dictionary-based row-level compression for SQLite」。

在文章裡面測出來的數據看起來在效能上就不一定有比較好的結果,本來的 SQLite 就已經處理的還不錯了。

但看起來是個還蠻有趣的東西,舉例來說,如果可以透過 WebAssembly 編譯,再配合之前的「把 SQLite 的 VFS 掛上 WebTorrent 的 PoC Demo」,可以省下不少傳輸的量...

可以看看後續會不會真的有人這樣幹 XD

直接用 SQLite 查詢 Excel 檔案的 XLite (還有 dsq)

Hacker News 上看到「Xlite: Query Excel and Open Document spreadsheets as SQLite virtual tables (github.com/x2bool)」這個專案,就如同說明,是一個支援讀取 Excel 檔案的 SQLite extension,原網站在 x2bool/xlite 這邊。

依照說明支援舊的 .xls 與新的 .xlsx 的格式,但不知道公式運算支援到什麼程度...

先 load extension:

sqlite3 # will open SQLite CLI
> .load libxlite

接著是建立 virtual table:

CREATE VIRTUAL TABLE class_data USING xlite(
    FILENAME './path/to/example.xlsx',
    WORKSHEET 'Class Data',
    RANGE 'A2:F'
);

接下來就可以搞事了:

SELECT COUNT(*), D FROM class_data GROUP BY D ORDER BY COUNT(*);

看起來是用 Rust + C 寫的,然後作者有提醒這是寫興趣的專案:

This project is experimental, use at your own risk. The project is developed in my free time as a way to learn Rust and database systems.

反倒是 Hacker News 討論串裡面提到了 multiprocessio/dsq 這個專案,看起來發展的比較久,支援度也比較完整了:

Commandline tool for running SQL queries against JSON, CSV, Excel, Parquet, and more

不過就不是綁 SQLite 了 (雖然還是有關),從範例可以看到他是獨立的程式:

$ dsq testdata.json "SELECT * FROM {} WHERE x > 10"

Under the hood dsq uses DataStation as a library and under that hood DataStation uses SQLite to power these kinds of SQL queries on arbitrary (structured) data.

如果是真的要用的話,這套看起來應該會好一些...