2023年6月17日土曜日

M2 mac mini (M2pro) で xmrig を動かす方法

M2 mac mini (M2pro) で xmrig を動かす方法

答え

docker を使いましょう

https://hawksnowlog.blogspot.com/2021/09/cpu-mining-monero-on-docker.html

segmentation fault について

Ventura 13.4 + xmrig 6.19.3 だと segmentation fault になります

CPU コア数について

また docker 経由だと cpu-max-threads-hint を 100 にしても 10 コアあるうちの 5 コアしかフルに使わなかったのでまだチューニングが必要そうです

↓

答えとしては docker for mac の設定で Resources から CPU コア数を 10 に上げましょう

ハッシュレート

ログだとだいたい 3000H/s ほどです
かなり少ない印象です
更にプール側で見ると 700H/s になってしまっているので何かおかしいような気がします (原因不明)

↓ 原因判明

Mac mini の場合もデフォルトだとモニタの電源が切れるとスリープモードに入ってしまうためマイニングもそれで停止してしまいます
システム設定 -> ディスプレイ -> 詳細設定 -> ディスプレイがオフのときに自動でスリープさせないにチェック

2023年6月16日金曜日

Flask でフォーム情報を受け取る方法 (jsonまたxmlでも可)

Flask でフォーム情報を受け取る方法 (jsonまたxmlでも可)

概要

前回 json or xml どちらでも受け取れるような仕組みを考えてみました

今回はさらに form でも受け取れるような API に変更します

環境

  • macOS 13.4
  • Python 3.11.3
  • Flask 2.3.2

サンプルコード

レスポンスの変換に関するコードも含まれています
ポイントは Convert クラスの is_form と from_form です
特定のキーが含まれているかでフォームかどうかを判断するようにしています
flask で入れ子なフォームデータを受け取ると Immutablemultidict がなぜか入れ子にならずそのままキー名として使われるようです

リクエスト情報を Converter.to_dict するとどんなデータでも dict に変換してくれるイメージです

  • vim app.py
import copy
import json
import traceback
from json.decoder import JSONDecodeError
from xml.parsers.expat import ExpatError

import dicttoxml
import xmltodict
from flask import Flask, Response, jsonify, request

app = Flask(__name__)


class Profile:
    def __init__(self) -> None:
        self.langs = ["ruby", "python", "swift"]

    def to_dict(self):
        return self.langs


# 各 langs の xml 変換時の属性名を設定
def set_item_name(_: str) -> str:
    return "lang"


class User:
    def __init__(self, name: str = "default_user", **_) -> None:
        self.name = name
        self.age = 10
        self.profile = Profile()

    def to_dict(self):
        return {"name": self.name, "age": self.age, "profile": self.profile.to_dict()}


# dict から User オブジェクトへの変換
def convert_obj(func):
    def wrapper(*args, content_dict: dict, **kwargs):
        try:
            user = User(**content_dict["xml"])
        except KeyError:
            # データ構造が正しくない場合はエラー
            raise ValueError("Root attribute must start with 'xml'.")

        return func(*args, user=user, **kwargs)

    return wrapper


# リクエストの raw xml や raw json を dict に変換するクラス
class Converter:
    def __init__(self, body: bytes, form: dict) -> None:
        self.body = body
        self.form = form

    def to_dict(self) -> dict:
        if self._is_xml():
            return self._from_xml()
        elif self._is_json():
            return self._from_json()
        elif self._is_form():
            return self._from_form()
        return {}

    def _is_xml(self) -> bool:
        try:
            self._from_xml()
        except ExpatError:
            return False
        return True

    def _is_json(self) -> bool:
        try:
            self._from_json()
        except JSONDecodeError:
            return False
        return True

    def _is_form(self) -> bool:
        try:
            self._from_form()
        except KeyError:
            return False
        return True

    def _from_xml(self) -> dict:
        return xmltodict.parse(self.body)

    def _from_json(self) -> dict:
        return json.loads(self.body)

    def _from_form(self) -> dict:
        return {"xml": {"name": self.form["xml[name]"]}}  # なぜか入れ子にならない?


# xml から dict への変換
def convert_dict(func):
    def wrapper(*args, **kwargs):
        body = copy.copy(request.data)
        form = copy.copy(request.form)
        data = Converter(body, form).to_dict()
        if not data:
            raise ValueError("You must be specified request body xml, json or form.")

        return func(*args, content_dict=data, **kwargs)

    return wrapper


@app.route("/", methods=["GET", "POST"])
@convert_dict
@convert_obj
def parse_xml(user: User):
    # デフォルトは json
    return jsonify(user.to_dict())


@app.after_request
def after_request(response: Response):
    accept_header = request.headers.get("Accept")
    if accept_header == "application/xml":
        # xml に変換
        xml = dicttoxml.dicttoxml(
            response.get_json(),
            attr_type=True,
            root=True,
            custom_root="orgRoot",
            item_func=set_item_name,  # type: ignore
        )
        response.headers["Content-Type"] = "applicatoin/xml"
        response.data = xml
    elif accept_header == "application/json":
        # json に変換 (そのまま返却)
        pass
    return response


@app.errorhandler(ValueError)
def handle_bad_request(e):
    return traceback.format_exception_only(e), 400

動作確認

  • pipenv run flask run
curl localhost:5000 -XPOST -d '{"xml": {"name":"hawksnowlog"}}' -H 'content-type: application/json'
curl localhost:5000 -XPOST -d '<xml><name>hawksnowlog</name></xml>' -H 'content-type: application/xml'
curl localhost:5000 -XPOST -d "xml[name]=hawksnowlog" -H 'content-type: application/x-www-form-urlencoded'

結果はすべて同じになります

2023年6月15日木曜日

Flask でレスポンス情報を Accept ヘッダに応じて xml or json に変換して返却する方法

Flask でレスポンス情報を Accept ヘッダに応じて xml or json に変換して返却する方法

概要

Flask でレスポンスを返却する際に xml or json を返却する方法を考えます

flask-restful という拡張を使うと簡単にできそうですが今回は素の flask だけを使って考えます

環境

  • macOS 13.4
  • Python 3.11.3
  • Flask 2.3.2
    • xmltodict 0.13.0
    • dicttoxml 1.7.16

方針

  • Accept ヘッダの内容に応じて返却するタイプを変更する
    • Accept: application/xml -> xml を返却
    • Accept: applicaton/json -> json を返却
  • すべてのルーティングに対応するため after_request を使う
  • 特に指定がない場合は json を返却する

サンプルコード

一部前回のコードを含みます
また xml レスポンスの情報を少し加工するための処理も入っています
ポイントはコード下部にある after_request の部分になります

  • vim app.py
import copy
import json
import traceback
from json.decoder import JSONDecodeError
from xml.parsers.expat import ExpatError

import dicttoxml
import xmltodict
from flask import Flask, Response, jsonify, request

app = Flask(__name__)


class Profile:
    def __init__(self) -> None:
        self.langs = ["ruby", "python", "swift"]

    def to_dict(self):
        return self.langs


# 各 langs の xml 変換時の属性名を設定
def set_item_name(_: str) -> str:
    return "lang"


class User:
    def __init__(self, name: str = "default_user", **_) -> None:
        self.name = name
        self.age = 10
        self.profile = Profile()

    def to_dict(self):
        return {"name": self.name, "age": self.age, "profile": self.profile.to_dict()}


# dict から User オブジェクトへの変換
def convert_obj(func):
    def wrapper(*args, content_dict: dict, **kwargs):
        try:
            user = User(**content_dict["xml"])
        except KeyError:
            # データ構造が正しくない場合はエラー
            raise ValueError("Root attribute must start with 'xml'.")

        return func(*args, user=user, **kwargs)

    return wrapper


# xml から dict への変換、すでに dict の場合は何もしない
def convert_dict(func):
    def wrapper(*args, **kwargs):
        data = copy.copy(request.data)
        try:
            data = xmltodict.parse(data)
        except ExpatError:
            try:
                data = json.loads(data)
            except JSONDecodeError:
                # xml じゃないかつ dict じゃない場合はエラー
                raise ValueError("You xml or dict must be specified.")

        return func(*args, content_dict=data, **kwargs)

    return wrapper


@app.route("/", methods=["GET", "POST"])
@convert_dict
@convert_obj
def parse_xml(user: User):
    # デフォルトは json
    return jsonify(user.to_dict())


@app.after_request
def after_request(response: Response):
    accept_header = request.headers.get("Accept")
    if accept_header == "application/xml":
        # xml に変換
        xml = dicttoxml.dicttoxml(
            response.get_json(),
            attr_type=True,
            root=True,
            custom_root="orgRoot",
            item_func=set_item_name,  # type: ignore
        )
        response.headers["Content-Type"] = "applicatoin/xml"
        response.data = xml
    elif accept_header == "application/json":
        # json に変換 (そのまま返却)
        pass
    return response


@app.errorhandler(ValueError)
def handle_bad_request(e):
    return traceback.format_exception_only(e), 400

動作確認

  • pipenv run python flask run
# xml
curl localhost:5000 -XPOST -d '<xml><name>hawksnowlog</name></xml>' -H 'content-type: application/xml' -H 'Accept: application/xml' 
<?xml version="1.0" encoding="UTF-8" ?><root><age type="int">10</age><name type="str">hawksnowlog</name></root>

# json
curl localhost:5000 -XPOST -d '{"xml": {"name":"hawksnowlog"}}' -H 'content-type: application/xml' -H 'Accept: application/json'
{
  "age": 10,
  "name": "hawksnowlog"
}

# json (指定なし)
curl localhost:5000 -XPOST -d '<xml><name>hawksnowlog</name></xml>' -H 'content-type: application/xml' 
{
  "age": 10,
  "name": "hawksnowlog"
}

最後に

Flask で Accept ヘッダの内容に応じてレスポンスの形式を変更するような API を考えてみました
やっていることは非常に単純ですがうまく作らないとコードが大変なことになりそうなので注意が必要そうです

(こういったマルチメディアタイプの API は実際に活用されているケースがあるのだろうか)

2023年6月14日水曜日

Flask でリクエストボディが xml でも json でもどちらでも受けれるようにするサンプルコード

Flask でリクエストボディが xml でも json でもどちらでも受けれるようにするサンプルコード

概要

content-type: application/xml or content-type: application/json どちらでも受けれるような API を考えます

環境

  • macOS 13.4
  • Python 3.11.3
  • Flask 2.3.2

方針

  • デコレータを使って xml -> dict をいい感じに変換する
  • 最終的には変換はオブジェクトに変換する
  • 各種エラー対応はできる限りしっかりしたい
  • レスポンスに対しては現在は考慮しない

サンプルコード

  • vim app.py
import copy
import json
import traceback
from json.decoder import JSONDecodeError
from xml.parsers.expat import ExpatError

import xmltodict
from flask import Flask, request

app = Flask(__name__)


class User:
    def __init__(self, name: str = "default_user", **_) -> None:
        self.name = name
        self.age = 10


# dict から User オブジェクトへの変換
def convert_obj(func):
    def wrapper(*args, content_dict: dict, **kwargs):
        try:
            user = User(**content_dict["xml"])
        except KeyError:
            # データ構造が正しくない場合はエラー
            raise ValueError("Root attribute must start with 'xml'.")

        return func(*args, user=user, **kwargs)

    return wrapper


# xml から dict への変換、すでに dict の場合は何もしない
def convert_dict(func):
    def wrapper(*args, **kwargs):
        data = copy.copy(request.data)
        try:
            data = xmltodict.parse(data)
        except ExpatError:
            try:
                data = json.loads(data)
            except JSONDecodeError:
                # xml じゃないかつ dict じゃない場合はエラー
                raise ValueError("You xml or dict must be specified.")

        return func(*args, content_dict=data, **kwargs)

    return wrapper


@app.route("/", methods=["GET", "POST"])
@convert_dict
@convert_obj
def parse_xml(user: User):
    return user.name


@app.errorhandler(ValueError)
def handle_bad_request(e):
    return traceback.format_exception_only(e), 400

動作確認

正常系

curl localhost:5000 -XPOST -d '{"xml": {"name":"hawksnowlog"}}' -H 'content-type: application/json'
curl localhost:5000 -XPOST -d '<xml><name>hawksnowlog</name></xml>' -H 'content-type: application/xml' -H 'Accept: application/xml'

異常系

curl localhost:5000 -XPOST -d '{"xml2": {"name":"hawksnowlog"}}' -H 'content-type: application/json'
curl localhost:5000 -XPOST -d 'hoge' -H 'content-type: application/json'

最後に

レスポンスは accept ヘッダのタイプに応じて勝手に変換してくれるようになっているとかっこいい

2023年6月13日火曜日

Vagrant で vmdk を圧縮する方法

Vagrant で vmdk を圧縮する方法

概要

VM 上では容量を使用していないのに知らぬ間に肥大化していることがあります
そんな場合に vmdk ファイルを圧縮する方法を紹介します

ファイル名や UUID の部分は適宜変更してください

環境

  • macOS 11.7.6
  • vagrant 2.3.4
  • VirtualBox 7.0.6

ゼロ埋め

  • sudo dd if=/dev/zero of=zero bs=4k; \rm zero

圧縮

  • cd "/Users/username/VirtualBox VMs/vm_default_xxxx/"
  • VBoxManage clonehd ubuntu-bionic-18.04-cloudimg.vmdk ubuntu-bionic-18.04-cloudimg.vdi --format vdi
  • VBoxManage modifyhd ubuntu-bionic-18.04-cloudimg.vdi compact
  • VBoxManage clonehd ubuntu-bionic-18.04-cloudimg.vdi ubuntu-bionic-18.04-cloudimg_2.vmdk --format vmdk

vdi ファイル削除

  • VBoxManage list hdds
  • VBoxManage closemedium disk cb44d740-7c42-41d2-b0cf-e3938e8e0ee8
  • rm buntu-bionic-18.04-cloudimg.vdi

ディスクデタッチ/アタッチ

  • VBoxManage list vms
  • VBoxManage storageattach 3fffffaa-db44-46b5-b669-77d21b173089 --storagectl "SCSI" --port 0 --device 0 --type hdd --medium "/Users/username/VirtualBox VMs/vm_default_xxxx/ubuntu-bionic-18.04-cloudimg_2.vmdk"

古い vmdk 削除

  • VBoxManage closemedium disk f8f59ec9-2d06-47c2-87c9-29604a72b38f
  • rm ubuntu-bionic-18.04-cloudimg.vmdk

動作確認

  • vagrant up
  • vagrant ssh
  • rm /home/vagrant/zero
  • sudo reboot -h now

で問題なく動作することを確認しましょう

最後に

自分の場合は 17GB -> 4GB ほどになりました

2023年6月12日月曜日

Stable Diffusion WebUI を M2pro mac mini 上で動かす方法

Stable Diffusion WebUI を M2pro mac mini 上で動かす方法

概要

前回 Intel mac 上で動作させました
今回は Apple Silicon の M2 mac mini 上で動作させてみました

環境

  • macOS 13.4
  • M2pro mac mini
  • Stable Diffusion WebUI 1.3.2
  • Python 3.11.3

インストール

前回の手順とほぼ同じです
gpu を使えるので cpu only のフラグは外します

  • git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui
  • cd stable-diffusion-webui
  • sh webui.sh

localhost:7860 にアクセスできれば OK です

1枚の生成にかかる時間

512x512 のサイズでステップ数 20 (すべてデフォルト設定) でだいたい 10 - 20 秒ほどで生成できます

モデルを変える場合は

基本は models/Stable-diffusion 配下に置くだけです

Lora などは models/Lora になります VAE は models/VAE になります

2023年6月11日日曜日

rqではワーカーに渡す関数はクラスのインスタンスメソッドでもよい

rqではワーカーに渡す関数はクラスのインスタンスメソッドでもよい

概要

エンキューする際に func 引数にインスタンスメソッドが指定可能か試してみました
結論としては指定可能です

環境

  • macOS 11.7.6
  • Python 3.10.2
    • rq 1.15.0
    • rq-scheduler 0.13.1

エンキュー

  • vim app.py
from redis import Redis
from rq import Queue
from rq_scheduler import Scheduler
from lib.util import Message

queue = Queue('bar', connection=Redis())
scheduler = Scheduler(queue=queue, connection=queue.connection)

scheduler.cron(
    "*/1 * * * *",
    func=Message("Hello!").say,
    args=[],
)

scheduler.run()

上記の func=Message("Hello!").say のポイントです
コーラブルであればいいのでインスタンスメソッドでも渡すことができます

ワーカー

  • vim lib/util.py
from datetime import datetime

class Message():
    def __init__(self, message: str):
        self.message = message

    def say(self):
        msg = f"{self.message}: {datetime.now()}"
        print(msg)
        return msg
  • vim worker.py
from rq import Worker, Queue
from redis import Redis

redis = Redis()
queue = Queue('bar', connection=redis)


if __name__ == "__main__":
    worker = Worker(queues=[queue], connection=redis)
    worker.work(with_scheduler=True)

動作確認

  • pipenv run python app.py
  • pipenv run python worker.py

最後に

これも pickle を使ってシリアライズしているため実現可能になっています
コンストラクタも呼ぶことができるので便利です
あとは引数と組み合わせればいろいろなケースに応じた処理ができるようになるかなと思います

rq の記事はいくつか紹介していますがワーカー側のコードはほぼ変わっていないのでコマンド実行でもいいかもしれません