2021年6月29日 星期二

DIY USB Hub 及 PS2 轉 USB 界面

 DIY USB HUB

因為改裝機械鍵盤,在機械鍵盤上加裝 Trackpoint 指點桿,需要用到 USB Hub 來整合鍵盤和指點桿的信號,這樣鍵盤就只要一條傳輸線。先前在露天買到很便宜的 USB Hub,一個只要 35元,雖然其宣稱只用來做電源分接,但買回來功能正常。原來買了 3個,改裝鍵盤都用完了,就想再找一樣便宜的來買。比較多家之後,找到一個最便宜的,圖片也清楚顯示 USB 2.0/1.1 HUB,然後一口氣買了4個。但這次運氣就不再那麼好了,買的全部是假的,真的只能當電源分接用。

下圖是在露天拍賣買的 USB HUB,可以看到止焊膜有裝 IC 的位置,但信號線全部是併聯的。不曉得大陸人為何要這麼費心的做個假貨來賣,不如把這功夫拿來做有用的東西。有時品質雖差,但至少能用。

我的目的是要把 Hub 拆開,電路板鋸短,整個藏到鍵盤裡。在網路上搜尋 USB Hub IC,發現有現成的 IC,只要加幾個簡單的元件就可以了,一顆 IC 最便宜的十幾元,雖然加上工時,價格遠超過直接買,但就當練功和 DIY 的樂趣吧,而且也不會買到從外觀無從判斷真假的 Hub。

這顆 IC 是 FE1.1S,USB 2.0 HIGH SPEED 4-PORT HUB CONTROLLER,是台灣湯銘出的晶片,很多 USB Hub 都用這顆 IC。最簡單能工作的電路如下。若有需要還可以自行加裝 LED 指示。


把一個古老的 USB HUB 用此晶片改裝成 USB 2.0 的 HUB,當作功能測試。


這顆是古早古早的聯強 Lemel 的產品。



DIY PS2 轉 USB 界面

使用的晶片 CSC0101A-S16G,是台灣巨盛的晶片,大陸的代理商為潤得源 (rundex),在網上找到的參考電路圖也潤得源提供的。CSC0101A 具有2個 PS2 port,可同時接鍵盤及滑鼠。在網路上找到另一個比較容易找到的 WIT122UH,只有一個 PS port,而且只能接鍵盤,因此不適用。

下圖為 CSC0101A-S16G 的參考電路圖。


內部是 65C02 8 Bit CPU,

- Conforms to USB specification, version 1.1

 - Conforms to USB HID specification, version 1.1

 - Supports 1 low speed device address and 3 endpoints

- 8 bytes FIFO for each endpoint 

2021年6月4日 星期五

熱塑土

 熱塑土

參考: https://forum.gamer.com.tw/Co.php?bsn=60053&sn=231725,https://kknews.cc/zh-tw/game/om4m5ro.html

討論: https://www.mobile01.com/topicdetail.php?f=368&t=5841369


重點整理

  • 不能用金屬容器操作,要用紙杯或紙碗(內有蠟塗層),才不會黏在容器裡
  • 免洗筷也是會黏住,建議用塑膠筷抹一層凡士林
  • 塑晶土一變透明就立刻取出塑形,不能泡到它都融掉
  • 不要放鍋子裏煮,用剛煮開的水, 九十度以上的去泡, 很快就透明變軟了
  • 可放碗裏,沖入熱水瓶的水,看到全透明就用湯匙推出來。如果推不乾淨太軟太黏,就水倒掉再推,然後再倒點熱水在碗裏。有需要時可以丟回去保溫回軟
  • 高溫 (飲水機出來) 會變得很黏很燙,黏鍋底,趁熱拿一團來反覆沾黏就可以去除。溫度降下來後,剛好是在手能操作的溫度會是透明不黏手。通常我是弄一盆冰水來加速硬化,還有一盆燙的,接點黏一點比較穩

網友經驗
我是用日本的水晶土,不過狀況跟你一樣:
-- 加熱不夠,一下就硬了,沒時間塑型
-- 加熱過頭,太燙了,沒辦法塑形
所以後來我改用泡"滾水",不要真的拿去煮
泡滾水取出後很燙,但還可以忍耐,塑形時間不長不短,但是操作一樣要迅速
-----
Super Maker 創塑土&熱塑土(第二代創塑土)特性比較篇
https://www.youtube.com/watch?v=4HabdnLfsGo
有一種是比較快硬化的
-----
我可以很肯定的跟你說,牌子絕對有差,以前我也買過瑞典製的,超級難用,就跟你敘述的反應方式一模一樣,
後來不信邪買了別的牌子來用(忘了台灣還是滔寶買的了).
完全不一樣,建議真的想玩,再買別的牌子試試吧

我的作法: 泡滾水,看到顆粒開始轉變透明內部還有點白芯(不要全部透明,太熱會變黏容器就要等冷一點再拿水晶黏土黏起來)

筷子稍攪拌黏住所有顆粒

拿起就能捏了,我後來用的從拿起,大約5分左右捏造型都還很好捏,大概要10分以上才會整個再變白色固體狀
-----

生活應用

門鎖推桿 (2021-12-01補記)。原來的推桿掉了,十幾年來都直接推中間的鐵柱,一不小心就會掉在地上要找半天。也找不到螺紋相合的螺絲來裝上,某天想到可以用旅行時買的奈米膠水黏根 M3 螺絲啊。而且膠水雖然黏得很牢,但只要用熱風機加熱就能取下,不用怕那天後悔要拆掉。螺絲頂部凸出摸起來不舒服,就想到用熱塑土試試看,成果還不錯,如下圖。




2021年5月12日 星期三

將 VM 從 ESXi 4.1 搬到 ESXi 6.7



用 Convert 在連 Server 時,就會出現 SSH Error

總之就是 SSH 加密升級造成的問題

參考這篇討論

https://communities.vmware.com/t5/VMware-vSphere-Discussions/SCP-between-4-1-and-6-7/td-p/490986

在 Linux 下使用 SSH 連 ESXi 4.1 會出現錯誤
$ ssh root@10.161.86.140
Unable to negotiate with 10.161.86.140 port 22: no matching key exchange method found. Their offer: diffie-hellman-group1-sha1

必需加上選項
ssh  -oKexAlgorithms=+diffie-hellman-group1-sha1  root@10.161.86.140

要從 ESXi 6.7 連 ESXi 4.1 有點麻煩,就用 Linux 當跳板。

不是那麼緊集的伺服器,可以關機 2, 3 個小時,就用 scp 先 copy 到 Linux,再 copy 到 ESXi 6.7 上。
另外,在 ESXi 6.7 上建一個 ESXi 5.5 的 VM,將伺服器用 Convert 5.5 轉到 ESXi 5.5 的主機上,然後再用 scp 複製到 ESXi 6.7 上。用 Convert 的好處是,可以先將伺服器上的檔清一清,可以減少傳送的資料。
因為直接 copy vmdk,thick 佈建的 vmdk,是完整的整個大小,如 30G 的 HD,大小就是 30G。傳送時間要比較久。

然後,外層的 ESXi 6.7 竟然不能和內部的 ESXi 5.5 用 ssh 連線。想說用 Gentoo Linux 來 mount ESXi 的 VMDK,又不認識,出現錯誤
 mount: unknown file system type 'VMFS_volume_member'
Google 說在 Ubuntu 下,可以安裝 vmfs-tools,就可以 mount,結果又找不到這個 package的錯誤。

最後還是用老方法,把convert之後的VM的資料全部先 copy 到 Linux,再copy 到 ESXi 6.7 裡面。然後就可以了。

2021年4月16日 星期五

使用 scrcpy 操作手機 (Gentoo)

先來張擷圖,可以透過電腦來操作手機,是蠻方便的,而且可以在 Linux 下使用。

Gentoo 官方的 portage 沒有 scrcpy 這軟體,必須找 overlay 的 ebuild。

安裝好後,第一次執行

ajax@aj-i7 ~ $ scrcpy
INFO: scrcpy 1.17 <https://github.com/Genymobile/scrcpy>
exec: No such file or directory
ERROR: Command not found: [adb], [push], [/usr/share/scrcpy/scrcpy-server], [/data/local/tmp/scrcpy-server.jar]
ERROR: (make 'adb' accessible from your PATH or define its fullpath in the ADB environment variable)
INFO: You may install 'adb' by "apt install adb"
ERROR: Could not execute "adb push"

找不到 adb 指令,關於 Gentoo 的 adb 說明: Android/adb

安裝 dev-util/android-tools package 之後再執行

ajax@aj-i7 ~ $ scrcpy
INFO: scrcpy 1.17 <https://github.com/Genymobile/scrcpy>
* daemon not running; starting now at tcp:5037
* daemon started successfully
adb: error: failed to get feature set: no devices/emulators found
ERROR: "adb push" returned with value 1

需要在手機開啟 USB debug,這個無需 root,一般是在 [系統資訊] 的某個項目上點 7下,就會出現開發者選項。

再次執行

ajax@aj-i7 ~ $ scrcpy
INFO: scrcpy 1.17 <https://github.com/Genymobile/scrcpy>
/usr/share/scrcpy/scrcpy-server: 1 fil...shed. 1.3 MB/s (34930 bytes in 0.025s)
[server] INFO: Device: samsung SM-A315G (Android 10)
[server] ERROR: Exception on thread Thread[main,5,main]
java.lang.IllegalStateException
	at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method)
	at android.media.MediaCodec.dequeueOutputBuffer(MediaCodec.java:2789)
	at com.genymobile.scrcpy.ScreenEncoder.encode(ScreenEncoder.java:113)
	at com.genymobile.scrcpy.ScreenEncoder.internalStreamScreen(ScreenEncoder.java:94)
	at com.genymobile.scrcpy.ScreenEncoder.streamScreen(ScreenEncoder.java:60)
	at com.genymobile.scrcpy.Server.scrcpy(Server.java:80)
	at com.genymobile.scrcpy.Server.main(Server.java:252)
	at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
	at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:342)
INFO: Renderer: opengl
INFO: OpenGL version: 4.6.0 NVIDIA 460.56
INFO: Trilinear filtering enabled
INFO: Initial texture: 1080x2400
WARN: Device disconnected

還是不行,但只差一步了,指令要加解析度

ajax@aj-i7 ~ $ scrcpy -m 1080
INFO: scrcpy 1.17 <https://github.com/Genymobile/scrcpy>
/usr/share/scrcpy/scrcpy-server: 1 fil...shed. 5.6 MB/s (34930 bytes in 0.006s)
[server] INFO: Device: samsung SM-A315G (Android 10)
INFO: Renderer: opengl
INFO: OpenGL version: 4.6.0 NVIDIA 460.56
INFO: Trilinear filtering enabled
INFO: Initial texture: 488x1080
  ==> 連上手機,開啟手機螢幕視窗
....
  ==> 關閉手機螢幕視窗
WARN: Killing the server...


旋轉90度的指令 "scrcpy -m 1080  --lock-video-orientation 1"


使用 appimage

 media-sound/jack2





2021年3月18日 星期四

Laravel-S (Swoole 加速) 使用重點整理

在此所做之重點整理,係針對使用 hhxsv5/laravel-s 來對 Laravel 開發的網站提供 Swoole 加速功能,其專案網站為

基本資料

作業系統安裝的軟體
CentOS: 7.9.2009
Apache: 2.4.6
PHP: 7.0.33
Supervisor: 3.4.0-1
PHP Composer: 1.10.20
PHP Swoole: 2.2.0

透過 Composer 安裝 PHP package
LaravelS: 3.6.4
Laravel Framework: 5.1.46 (LTS)

Docker 設定

dockerfile
FROM centos:7.9.2009

RUN yum -y install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm \
    && yum -y update \
    && yum -y install --enablerepo=remi,remi-php70 \
        httpd php php-pdo php-pgsql \
        php-xml php-mbstring php-mcrypt \
        php-opcache php-pecl-swoole2 php-pecl-inotify \
        unzip supervisor \
    && yum clean all \
    && rm -rf /var/cache/yum \
    && curl -o /usr/bin/composer https://getcomposer.org/download/1.10.20/composer.phar \
    && chmod a+x /usr/bin/composer

# add config file
COPY conf/httpd.conf /etc/httpd/conf/httpd.conf
COPY conf/supervisord.conf /etc/supervisord.conf
COPY conf/rev_proxy.conf /etc/httpd/conf.d/

#port and entry
EXPOSE 80

CMD ["/usr/bin/supervisord"]

其中,php-pecl-swoole2 為 PHP 加 Swoole 功能,php-pecl-inotify 則是為了偵測檔案變動重新載入程式。

rev_proxy.conf
<IfModule deflate_module>
    SetOutputFilter DEFLATE
    DeflateCompressionLevel 2
    AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml
</IfModule>

RemoteIPHeader X-Forwarded-For

ProxyRequests Off
ProxyPreserveHost On

supervisord.conf
[supervisord]
nodaemon=true
logfile=/var/log/supervisord.log
pidfile=/var/run/supervisord.pid

[program:ntuocw_laravels]
command=php /var/www/html/ntu-ocw/web-src/bin/laravels start
redirect_stderr=true
autostart=true
autorestart=true
#user=http
numprocs=1
process_name=%(program_name)s_%(process_num)s
stdout_logfile=/var/www/html/ntu-ocw/storage/logs/bin_laravel-s-log1.log
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=5


[program:httpd]
redirect_stderr=true
command=/usr/sbin/httpd -DFOREGROUND
process_name = httpd

對了,開發測試時,請手動下指令啟動,不要使用 supvervisord 啟動,若程式有問題,會陷入 reload 的無限迴圈。

httpd.conf 則只是要加上 "AllowOverride All",允許在目錄下使用 .htaccess 加上 rewrite rule。更重要的是基於安全,關閉目錄瀏覽功能 "Options -Indexes"。

使用 docker-compose 管理 docker,主要是下面三種指令
docker-compose build  ==> 建立 image
docker-compose up     ==> 啟動
docker-compose up -d  ==> 啟動,在背景執行
docker-compose down   ==> 停止

docker-compose.yml
version: '2'

services:
  web:
    build:
      context: ./
    #  dockerfile: Swoole_dockerfile
    restart: always
    working_dir: /var/www/html
    volumes:
      - ../web:/var/www/html
      - ../work_tmp:/work_tmp
    environment:
      COMPOSER_HOME: /work_tmp/Composer
    ports:
      - 8180:80


基本設定

以普通使用者的身份進入 container 中。
docker exec -it --user 1000:1000 ocw_docker_web_1 bash

進入後,建立 Laravel 5.1 的專案。
composer create-project laravel/laravel ntu-ocw "5.1.*"
在 composer.json 加入。
"require": {
    "php": ">=5.5.9",
    "laravel/framework": "5.1.*",
    "hhxsv5/laravel-s": "3.6.*"
},

然後執行 composer update,更新和安裝 PHP package

在 config/app.php,加入
'providers' => [
    //...
    Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class,
],

不然在執行下述指令時,會出現 Command "laravels" is not defined

php artisan laravels publish

若出現無法寫入 storage 的檔案時,
sudo chmod -R a+w storage/


執行 publish 指令後會複製下列 4個檔案
config/laravels.php
bin/laravels
bin/fswatch
bin/inotify


修改 .htaccess,為了確定修改正確,可將 index.php 刪除。
DirectoryIndex index.php   ==> Apache 預設為 index.html

# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$  http://localhost:10520/$1 [P,L]

在 .env 加入,再重新啟動 bin/laravels。測試時,worker_num 不用設太多。
# configs for Laravel-S
LARAVELS_LISTEN_PORT=10520
LARAVELS_WORKER_NUM=5
LARAVELS_INOTIFY_RELOAD=true
執行後,使用 ps -ax 看到的執行緒
  PID TTY      STAT   TIME COMMAND
      1 ?        Ss     0:01 /usr/bin/python /usr/bin/supervisord
      8 ?        Sl     0:00 /var/www/html/ntu-ocw/web-src laravels: master process
      9 ?        S      0:00 /usr/sbin/httpd -DFOREGROUND
     22 ?        S      0:00 /var/www/html/ntu-ocw/web-src laravels: manager process
     28 ?        S      0:00 /var/www/html/ntu-ocw/web-src laravels: worker process 0
     29 ?        S      0:00 /var/www/html/ntu-ocw/web-src laravels: worker process 1
     30 ?        S      0:00 /var/www/html/ntu-ocw/web-src laravels: worker process 2
     31 ?        S      0:00 /var/www/html/ntu-ocw/web-src laravels: worker process 3
     32 ?        S      0:10 /var/www/html/ntu-ocw/web-src laravels: worker process 4
     33 ?        S      0:00 /var/www/html/ntu-ocw/web-src laravels: inotify process
     72 pts/0    Ss     0:00 bash
    111 ?        S      0:00 /usr/sbin/httpd -DFOREGROUND
    203 ?        Z      0:00 [httpd] <defunct>
    227 ?        S      0:01 /usr/sbin/httpd -DFOREGROUND
      ........
    265 ?        S      0:00 /usr/sbin/httpd -DFOREGROUND
    266 pts/0    R+     0:00 ps -ax


可修改 config/laravels.php,增加監視的檔案 .env
'inotify_reload'           => [
        'enable'        => env('LARAVELS_INOTIFY_RELOAD', false),
        'watch_path'    => base_path(),
        'file_types'    => ['.php', '.env'],
        'excluded_dirs' => [],
        'log'           => true,
    ],

在 app/Providers/AppServiceProvider.php 的 boot() 中,加入 laravels.received_request 事件的處理程式片段,因為要處理的事情太多,將程式寫在另外的檔案中。
class AppServiceProvider extends ServiceProvider
{
    // Bootstrap any application services.
    public function boot()
    {
        // 送交 Laravel 前的處理
        \Event::listen('laravels.received_request', function (\Illuminate\Http\Request $req, $app) {
            // 這裡才是真的透過 browser瀏覽
            // 才有某些關於 client 的 SERVER 資料
            \App\Lib\SW_util::boot_setup($req, $app);
        });

        // Laravel 結束後的處理
        \Event::listen('laravels.generated_response', function (\Illuminate\Http\Request $req, \Symfony\Component\HttpFoundation\Response $rsp, $app) {
            // $rsp->headers->set('Content-Type', 'image/png');// Change header of response
        });
    }

為了處理使用 Laravel-S 帶來的差異性,把相關的程式碼統一放在 app\Lib\SW_util.php 中。下面的 boot_setup() 專門用來處理 boot 的事情。
// boot() 的啟始程序
public static function boot_setup(\Illuminate\Http\Request $req, $app)
{
    // 在這裡 print 的訊息,都會出現在執行 laravels start 的 console
    // echo "\n\n===== laravels.received_request =====\n";

    session()->clear();

    // 去除附加的預設執行檔 index.php
    $req_uri = $req->server->get("REQUEST_URI");
    $d_php = '/index.php';  // 配合 .htaccess 的 DirectoryIndex 設定

    if (starts_with($req_uri, $d_php)) {
        $req_uri = substr($req_uri, strlen($d_php));

        if ($req_uri == '') {
            $req_uri = '/';
        }

        $req->server->set("REQUEST_URI", $req_uri);
    }

    // 把 IP 改成實際的 remote IP
    $req->server->set("REMOTE_ADDR", $req->server->get("HTTP_X_FORWARDED_FOR"));

    // $_SERVER["SCRIPT_FILENAME"] =>
    // "/var/www/html/ajtest/ntu-ocw/web-src/bin/laravels"
    $pre_s = '/var/www/html/';
    $tail_s = '/web-src/bin/laravels';
    $url_path = substr($req->server->get("SCRIPT_NAME"), strlen($pre_s));
    $url_path = substr($url_path, 0, (0-strlen($tail_s)));

    \URL::forceRootUrl('http://'.$req->server->get('HTTP_HOST').'/'.$url_path);

    self::$dump_handler = null;
}


在 boot() 中的 laravels.received_request 的事件要處理的重點包括下列各點
  • 修改 SERVER['REQUEST_URI'],移除 index.php
  • session()->clear(),清除前一個連線留下的 session 資料
  • 使用 URL::forceRootUrl() 設定 host 的 url,url() 和 asset() 產生的網址才會正確
  • 以 SERVER ['HTTP_X_FORWARDED_FOR'] 取代 SERVER['REMOTE_ADDR'],這樣 Request::ip() 才能取得正確的使用者 IP
  • 假如有用 static variables 存放連線運作的變數,要記得在此重置

在剛開始設定 Laravel-S 可以連線時,要開啟目錄的預設網頁時,一直會出現無法 match route 的錯誤,真是讓人感到挫折灰心。後來根據錯誤訊息,追縱 Laravel 的原始碼,直接找到 Illuminate\Routing\Router.php,在出錯的那個 function,把傳過來 url 列印出來,才發現收到的 httpd 送來的預設檔名,index.html,後來自己改成 index.php。在執行 match route 之前必須將 index.php 移除。在 Symfony 中,有相關程式的片段。

Session 全部變成一樣了

將系統切換之後,在測試留言功能時,發現怎麼我第一次用,就有亂七八糟的留言內容,並且顯示驗證碼錯誤。再仔細檢查,不得了,所有的 session 內容全部變成一樣了,裡面還有我剛剛登入管理端的帳號身份。因為下班後才發現,只好把系統先關掉,將 session 全刪掉,再重新啟動系統。使用者不能留言,也就只好忍耐一下囉,終究先前曾經因系統設定錯誤,一兩個月留言功能都不正常,也沒人反應。

短時間內,找不到其他使用者的相關經驗分享,或是 Laravel-S 對 session 處理的相關說明。就先自己追蹤程式吧,發現是 Laravel 的 session/store.php 中,會將 attributes 陣列的資料與儲存的 session 資料合併。而 Session store 只在 laravel-s start 時 Instance 一次,因而其 attributes array 會保留前一次連線的 session 資料。幸好此系統僅是供使用者瀏覽,只有一個網頁讓使用者留言用,session 不正確沒有造成太大的影響。

Laravel 框架是使用 Symfony 的 Session,其 Session Store 是 Implement Symfony 的 SessionInterface。在實際運作時,建立的 session 資料是放在 attributes 陣列中,等到連線結束時,才寫回儲存裝置中。在緊急情況下,直接修改 store.php,在 function start() 中直接將 attributes 陣列清空。後來找到 interface 中有提供 function clear(),其功能即是將 attributes 陣列清空。

要取得 session 物件有幾種方式,session() 或 $req->session(),在 boot 時, session 還沒加進 $req 中,呼叫 $req->session() 會出現錯誤 "Session store not set on request"。還好可以正常呼叫 session()->clear(), 將 attributes 陣列清除。

依照上面的作法,將問題解決了。過了一陣子,隨意搜尋關於 Larave-S 的資訊,發現早就有人反應此問題,甚至在最早查資料時,就曾看過有關的敘述,只是那時一點概念都沒有,不曉得他在說什麼。而且 Larave-S 也解決了此問題,在 setting 中有相關說明,只是自己不能理解。

解決辦法很簡單,在 config/laravels.php 中的 'cleaners' 加入設定即可。

// Need to configure the following cleaners if you use the session/authentication/passport in your project
'cleaners' => [
    Hhxsv5\LaravelS\Illuminate\Cleaners\SessionCleaner::class,
    Hhxsv5\LaravelS\Illuminate\Cleaners\AuthCleaner::class,
],

這樣就可以了,就這麼簡單,真是踏破鐵鞋方覓得,嗯,頂多是多按了一些鍵盤啦。

調整目錄

調整後的目錄大致是這樣

ntu-ocw
   +- assets
   |     +- css, js, img, .....
   +- vendor
   |     +- hhxsv5, laravel, .....
   +- storage
   |     +- app, framework, logs, ....
   +- web-src
   |     +- bin, app, config, ....
   |     +- storage (僅供 laravels 存放資料 laravels.json  laravels.pid)
   + .htaccess, favicon.ico, robots.txt


因為改變了 vendor 的位置,修改 composer.json,然後再次執行 composer update
"config": {
    "preferred-install": "dist",
    "vendor-dir": "../vendor"
}

bin/laravels 的修改,將 $basePath 以 BASEDIR 取代
// $basePath = realpath(__DIR__ . '/../');
// BASEDIR, VENDORDIR 同時要定義在 artisan 中
define('BASEDIR', realpath(__DIR__ . '/../'));
define('VENDORDIR', realpath(__DIR__.'/../../vendor'));

$loader = new Psr4Autoloader();
$loader->register();

// Register laravel-s
$loader->addNamespace('Hhxsv5\LaravelS', VENDORDIR . '/hhxsv5/laravel-s/src');

// Register laravel-s dependencies
$loader->addNamespace('Symfony\Component\Console', VENDORDIR . '/symfony/console');
$loader->addNamespace('Symfony\Contracts\Service', VENDORDIR . '/symfony/service-contracts');
$loader->addNamespace('Symfony\Contracts', VENDORDIR . '/symfony/contracts');

$command = new Hhxsv5\LaravelS\Console\Portal(BASEDIR);
$input = new Symfony\Component\Console\Input\ArgvInput();
$output = new Symfony\Component\Console\Output\ConsoleOutput();
$code = $command->run($input, $output);
exit($code);

laravel-s 會呼叫 artisan,因此其亦要配合修改
define('BASEDIR', __DIR__.'');
define('VENDORDIR', realpath(__DIR__.'/../vendor'));

require BASEDIR.'/bootstrap/autoload.php';
$app = require_once BASEDIR.'/bootstrap/app.php';
$app->useStoragePath(realpath(BASEDIR.'/../storage'));

bootstrap/app.php 的修改,將 storage 移到 web-src 外部,便於備份程式。但是 laravel-s 將storage 的目錄寫死在程式,在 web-src 下保留一個 storage 目錄,只供 laravel-s使用。
$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

$app->useStoragePath(realpath(BASEDIR.'/../storage'));

bootstrap/autoload.php 的修改
require VENDORDIR.'/autoload.php';

效能測試

花了這麼多時間了解,查資料,追蹤,思考,測試,除錯,才把系統套到 Laravel-S,用上 Swoole 加速的功能,總會想了解花費的心血得到多少的收益。若是收效不大,大可不用,造成維護上的麻煩。

下面先用 siege 做壓力測試,看看網站每秒最多能處理多少連線。再用 curl 觀察個別連線花費的時間。

使用 siege 做壓力測試

PHP 7.0+Swoole 2.2,在對外開放的網站上,同時有其他使用者連上來
$ siege -c 100 -t 5s http://ocw.aca.ntu.edu.tw/ntu-ocw/ > /dev/null

** SIEGE 4.0.7
** Preparing 100 concurrent users for battle.
The server is now under siege...

Lifting the server siege...
Transactions:		         769 hits
Availability:		      100.00 %
Elapsed time:		        4.54 secs
Data transferred:	       14.84 MB
Response time:		        0.56 secs
Transaction rate:	      169.38 trans/sec
Throughput:		        3.27 MB/sec
Concurrency:		       94.54
Successful transactions:         769
Failed transactions:	           0
Longest transaction:	        1.65
Shortest transaction:	        0.03

PHP 7.0,在正式網站上,為了測試臨時開啟,未對外開放。
$ siege -c 100 -t 5s http://ocw.aca.ntu.edu.tw:8180/ntu-ocw/ > /dev/null

** SIEGE 4.0.7
** Preparing 100 concurrent users for battle.
The server is now under siege...

Lifting the server siege...
Transactions:		         863 hits
Availability:		      100.00 %
Elapsed time:		        4.28 secs
Data transferred:	       27.31 MB
Response time:		        0.40 secs
Transaction rate:	      201.64 trans/sec
Throughput:		        6.38 MB/sec
Concurrency:		       81.50
Successful transactions:         863
Failed transactions:	           0
Longest transaction:	        2.98
Shortest transaction:	        0.00

測試結果,很意外的,使用 Swoole 加速的網站,效能反而比較差。猜測可能的原因,網站會受到各種的安全防護的包圍過濾封包,防護裝置的效能也會影響效能。另外,其他使用者會佔用連線資料,如查詢資料庫,花較多的時間,也會擋到測試的連線。而專為測試開啟的伺服器,則不受其他使用者連線的影響。

為了排除其他使用者的影響,以及受安全防護裝置的拖累,另外在開發用的測試環境再跑一次壓力測試。不論正式及開發環境,都是使用 docker 提供服務。

PHP 7.0+Swoole 2.2,在開發用的測試網站上
$ siege -c 100 -t 5s http://10.161.86.117:8180/ntu-ocw/ > /dev/null

** SIEGE 4.0.7
** Preparing 100 concurrent users for battle.
The server is now under siege...

Lifting the server siege...
Transactions:		       30103 hits
Availability:		      100.00 %
Elapsed time:		        4.05 secs
Data transferred:	     1053.04 MB
Response time:		        0.01 secs
Transaction rate:	     7432.84 trans/sec
Throughput:		      260.01 MB/sec
Concurrency:		       98.84
Successful transactions:       30103
Failed transactions:	           0
Longest transaction:	        0.25
Shortest transaction:	        0.00

PHP 7.0,在開發用的測試網站上
$ siege -c 100 -t 5s http://10.161.86.117:8080/ajtest/ntu-ocw/ > /dev/null

** SIEGE 4.0.7
** Preparing 100 concurrent users for battle.
The server is now under siege...

Lifting the server siege...
Transactions:		        2664 hits
Availability:		      100.00 %
Elapsed time:		        4.03 secs
Data transferred:	      361.01 MB
Response time:		        0.15 secs
Transaction rate:	      661.04 trans/sec
Throughput:		       89.58 MB/sec
Concurrency:		       97.29
Successful transactions:        2664
Failed transactions:	           0
Longest transaction:	        0.31
Shortest transaction:	        0.03

在測試環境得到結果,使用 Swoole 加速每秒大約可以處理 10倍的連線。對了,在這網頁有用到 Laravel 的 cache 功能,會將資料庫查詢 cache 在檔案中,因此不受資料庫效能的影響。

使用 curl 測單一連線時間

另一方面,測試個別連線的花費時間
寫個簡單的 script,curltime
#!/bin/bash

curl -w @- -o /dev/null -s "$@" <<'EOF'
    time_namelookup:  %{time_namelookup}\n
       time_connect:  %{time_connect}\n
    time_appconnect:  %{time_appconnect}\n
   time_pretransfer:  %{time_pretransfer}\n
      time_redirect:  %{time_redirect}\n
 time_starttransfer:  %{time_starttransfer}\n
                    ----------\n
         time_total:  %{time_total}\n
EOF

PHP 7.0+Swoole 2.2,在對外開放的網站上,同時有其他使用者連上來
$ ./curltime http://140.112.161.116/ntu-ocw/
    time_namelookup:  20
       time_connect:  2286
    time_appconnect:  0
   time_pretransfer:  2303
      time_redirect:  0
 time_starttransfer:  19828
                    ----------
         time_total:  30103

PHP 7.0,在正式網站上,為了測試臨時開啟,未對外開放。
$ ./curltime http://140.112.161.116:8180/ntu-ocw/
    time_namelookup:  92
       time_connect:  2724
    time_appconnect:  0
   time_pretransfer:  2739
      time_redirect:  0
 time_starttransfer:  94360
                    ----------
         time_total:  101133

從結果的數來看,時間單位應是 microseconds。

為了減少 DNS 查詢時間,使用 IP 連線。花費的時間會一直變動,使用 Swoole 加速,每個連線花費的時間大約在 30ms 左右。而未加速時,花費時間大約為 100ms 左右。

以上使用正式開放的網站測試,可能受網站安全防護或其他使用者連線的影響。接著使用測試環境來測試,比較能看到純粹網站的效能。

PHP 7.0+Swoole 2.2,在開發用的測試網站上
$ ./curltime http://10.161.86.117:8180/ntu-ocw/
    time_namelookup:  89
       time_connect:  295
    time_appconnect:  0
   time_pretransfer:  400
      time_redirect:  0
 time_starttransfer:  13112
                    ----------
         time_total:  13402

PHP 7.0,在開發用的測試網站上
$ ./curltime http://10.161.86.117:8080/ajtest/ntu-ocw/
    time_namelookup:  17
       time_connect:  70
    time_appconnect:  0
   time_pretransfer:  110
      time_redirect:  0
 time_starttransfer:  23723
                    ----------
         time_total:  24125

使用 Swoole 加速,每個連線花費的時間大約在 15ms 左右。而未加速時,花費時間大約為 25ms 左右。

2021年3月6日 星期六

Gentoo 與 GeForce 9500 GT

[  189.317025] nvidia-nvlink: Nvlink Core is being initialized, major device number 245
[  189.317618] NVRM: The NVIDIA GeForce 9500 GT GPU installed in this system is
               NVRM:  supported through the NVIDIA 340.xx Legacy drivers. Please
               NVRM:  visit http://www.nvidia.com/object/unix.html for more
               NVRM:  information.  The 455.28 NVIDIA driver will ignore
               NVRM:  this GPU.  Continuing probe...
[  189.317642] NVRM: No NVIDIA graphics adapter found!
[  189.317927] nvidia-nvlink: Unregistered the Nvlink Core, major device number 245


 [I] x11-drivers/nvidia-drivers
     Available versions:  ~390.132-r4(0/390)^mtd 390.138-r4(0/390)^mtd 435.21-r6(0/435)^mtd 440.100-r2(0/440)^mtd 450.80.02(0/450)^mtd 455.28(0/455)^mtd ~455.38(0/455)^mtd {+X compat +driver gtk3 +kms +libglvnd multilib static-libs +tools uvm wayland ABI_MIPS="n32 n64 o32" ABI_S390="32 64" ABI_X86="32 64 x32" KERNEL="FreeBSD linux"}
     Installed versions:  455.28(0/455)^mtd(03時17分06秒 西元2020年11月13日)(X driver gtk3 kms libglvnd multilib tools wayland -compat -static-libs -uvm ABI_MIPS="-n32 -n64 -o32" ABI_S390="-32 -64" ABI_X86="32 64 -x32" KERNEL="linux -FreeBSD")
     Homepage:            https://www.nvidia.com/Download/Find.aspx
     Description:         NVIDIA Accelerated Graphics Driver


[I] sys-firmware/nvidia-firmware
     Available versions:  (~)340.32-r1^md
     Installed versions:  340.32-r1^md(17時50分07秒 西元2021年02月15日)
     Homepage:            https://nouveau.freedesktop.org/wiki/VideoAcceleration/
     Description:         Kernel and mesa firmware for nouveau (video accel and pgraph)

2021年3月3日 星期三

Laravel-S (Swoole 加速) 的初步探索整理

在測試了最熱門的 swooletw/laravel-swoole 不成功之後,再測熱門程度差了好幾個等級,甚至 Google "laravel swoole" 的關鍵字,在搜尋結果都不會出現的 hhxsv5/laravel-s,成功了,那就繼續探所下去吧。

首先,新手真的不曉得該如 rewrite rule,轉過來的 request,有啟動 php 程式,但一直出 match route 錯誤,真是讓人感到挫折灰心。後來根據錯誤訊息,追縱 Laravel 的原始碼,直接找到 Illuminate\Routing\Router.php,在出錯的那個 function,加上下列程式片段,這樣它就會在執行的命令視窗顯示轉過來的網址,再依此結果修正 rewrite rule。

findRoute($request)
{
    echo "findRoute\n";
    var_dump($request->getRequestUri()); exit;
    ....
} 

發現在沒有指定檔名,要開啟預設檔案時,會轉過來 index.html,解決方式是在 .htaccess 加入。
DirectoryIndex index.php

然後在 route.php 將 Route::get('/', ... 改成如下
Route::get('/index.php', function () { ... });
在 Laravel 中使用的 dump() 是 Symfony\Component\VarDumper::dump() 建立的 helper function,會呼叫 CliDumper(),其會將資料輸出到執行 laravels start 的命令視窗。

解決辦法,參考原程式,自己另建一個 dump()

static 變數的測試,噢,會互相影響,所以要改很多 ...,只能再看看吧!!

另一困擾,假如網頁不是放在伺服器的 DocumentRoot 目錄下,asset() 和 url() 不會加上網頁的目錄,如 http://ll.cc/blog/list ...,其中 blog 不會出現。這要改的也是不少。

解決辦法是修改檔案 app/Providers/AppServiceProvider.php 中的 boot method。
public function boot()
{
    \URL::forceRootUrl("http://localhost:8180/blog"); 
}

亦可將其定義在 .env 中,例如 APP_URL。只要加上兩行就能解決問題,算是不錯了。

經過反覆測試,最後修改 boot() 的內容如下,程式的其餘部份不用改太多就可順利執行。
/*
 * 此為 laravels.received_request, After LaravelS parsed Swoole\Http\Request 
 * to Illuminate\Http\Request, before Laravel's Kernel handles this request.
 */
public function boot()
{
    // 只會在 loading 時被 call 一次
    echo "===================\n";

    \Event::listen('laravels.received_request', 
        function (\Illuminate\Http\Request $req, $app) 
        {
            // 這裡才是真的透過 browser瀏覽
            // 才有某些關於 client 的 SERVER 資料
            // 去除附加的預設執行檔 index.php
            $req_uri = $req->server->get("REQUEST_URI");
            $def_php = '/index.php';

            if (substr($req_uri, 0, strlen($def_php)) == $def_php) {
                $req_uri = substr($req_uri, strlen($def_php));
                if ($req_uri == '') {
                    $req_uri = '/';
                }

                $req->server->set("REQUEST_URI", $req_uri);
            }

            // 看起來,root url 並不是依據 "HTTP_HOST" 產生的
            // 改變 "HTTP_HOST",並不會改變 url() 和 asset() 的結果
            // 但是又找不到其他變數含有 port 資料
            // $req_host = $req->server->get("HTTP_HOST");
            // echo $req_host."<br>\n";
            // $req->server->set("HTTP_HOST", $req_host.'abc');
            // $req->server->set("HTTP_HOST", env('URL_PATH'));
            var_dump($req->request->all());
            echo base_path()."<br>\n";
            // initialize static varibles
            Util_sw::clean();
            // $req->query->set('get_key', 'hhxsv5');// Change query of request
            // $req->request->set('post_key', 'hhxsv5'); // Change post of request
            // echo asset(env('URL_PATH'))."<br>\n";
            \URL::forceRootUrl('http://'.$req->server->get('HTTP_HOST').'/'.env('URL_PATH'));
        }
    );
    
    // echo request()->getPathInfo();
    // echo \URL::to('/')."<br>\n";
    // var_dump(getallheaders());
    // \URL::forceRootUrl(env("APP_URL")); 
}

在上面的程式片段中,加了一堆測試輸出訊息的程式,就是在摸索階段為了瞭解其特性而加的。其餘的再繼續整理中。

依照 Laravel-S 專案上的說明,將 Laravel 的 .htaccess 的內容修改如下,就是在檔案不存在時,轉到 laravel-s 伺服程式處理。而 Apache 的 httpd,在瀏覽目錄時,設定的預設程式是 index.html,在這裡把它改成 index.php。

# Alternate default index page
DirectoryIndex index.php

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews
    </IfModule>

    RewriteEngine On

    # Redirect Trailing Slashes If Not A Folder...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)/$ /$1 [L,R=301]

    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$  balancer://laravels/$1 [P,L]
</IfModule>

針對上面的 rewrite rule,要修改 reverse proxy 的設定。
<IfModule deflate_module>
    SetOutputFilter DEFLATE
    DeflateCompressionLevel 2
    AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml
</IfModule>


RemoteIPHeader X-Forwarded-For

ProxyRequests Off
ProxyPreserveHost On

<Proxy balancer://laravels>  
    BalancerMember http://127.0.0.1:5200 loadfactor=7
    #BalancerMember http://192.168.1.2:5200 loadfactor=3
    #BalancerMember http://192.168.1.3:5200 loadfactor=1 status=+H
    ProxySet lbmethod=byrequests
</Proxy>

因為一個伺服器不只一個網頁程式,所以並未依照 Laravel-S 的說明,將網頁指向根目錄 (/)。為了解決此問題,測試了好久才搞定。


另外,不想修改網站設定,依自己的習慣調網站的目錄架構,將 public 目錄下的東西移到網頁的根目錄,程式則移到 web-src 下面,storage 不太好移,仍然放在 web-src 下,所以還需進一步修改 bin/laravels.php、artisan,bootstrap/autoload.php。


後續發展,經過半個多月的摸索,終於將一個自己維護的網站套上 Laravel-S 了,後面再開一篇文章來整理修改和注意的重點。





網誌存檔