Category Archives: Linux

Linuxに注文の多い料理店をしゃべらそう(Open Jtalk)

Nano Pi Neo2にOpen JTalkでしゃべらせたのでメモ。

Open Jtalkは「入力された日本語テキストに基づいて自由な音声を生成するHMMテキスト音声合成システム」とのこと。
http://open-jtalk.sp.nitech.ac.jp/

open-jtalk、辞書をインストールする。
open-jtalkをインストールすれば辞書もついてくる。

[bash]$ apt search open-jtalk
ソート中… 完了
全文検索… 完了
open-jtalk/artful 1.10-1 amd64
日本語音声合成システム

open-jtalk-mecab-naist-jdic/artful,artful,now 1.10-1 all
NAIST Japanese Dictionary for Open JTalk

$ sudo apt-get install open-jtalk
パッケージリストを読み込んでいます… 完了
依存関係ツリーを作成しています
状態情報を読み取っています… 完了
提案パッケージ:
hts-voice-nitech-jp-atr503-m001
以下のパッケージが新たにインストールされます:
open-jtalk open-jtalk-mecab-naist-jdic
(略)
[/bash]

提案パッケージであるhts-voice-nitech-jp-atr503-m001も。
これは音声データ。男性の声。

[bash]hts-voice-nitech-jp-atr503-m001 – Japanese male voice data for Open JTalk[/bash]

open-jtalkのコマンドはopen_jtalk(間はアンダースコア)として、open_jtalkの辞書は/var/lib/mecab/dic/open-jtalk/naist-jdicに、音声データは/usr/share/hts-voice/にインストールされる。
「こんにちは」と書いたvoice.txtを作り、open_jtalkに食わす。

[bash]$ cat ./voice.txt
こんにちは

$ open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow test.wav ./voice.txt [/bash]

喋ってくれるはず。
やはり女性の声のほうが良いので、名工大のMMDAgentを使う。
http://share.udialogue.org/meissen/login.htm
2018/3/21時点で1.7が最新
ダウンロードして展開し、/usr/share/hts-voice/に格納する。
使うのは中身の「mei」ディレクトリ配下のみ。
Meiさんは名工大キャラクターだそうな。

[bash]$ wget https://sourceforge.net/projects/mmdagent/files/MMDAgent_Example/MMDAgent_Example-1.7/MMDAgent_Example-1.7.zip
$ unzip ./MMDAgent_Example-1.7.zip
$ sudo cp -R ./MMDAgent_Example-1.7/Voice/mei /usr/share/hts-voice/[/bash]

声には以下の5種類がある。実際に発声させる際に選ぶ。
mei_angry.htsvoice mei_happy.htsvoice mei_sad.htsvoice
mei_bashful.htsvoice mei_normal.htsvoice

以下のスクリプトを用意する。
どこかのサイトから持ってきたんですが、どのサイトからだったか忘れてしもうた…。
これをjtalk.pyとして保存する。1行目は環境に応じて変えること。
htsvoiceは上記の通り喜怒哀通常から選べる。
speedも選べる。
このあたりは試行錯誤。

あとはこれをimportして使う。

[python]#!/usr/bin/python

#coding: utf-8
import subprocess
from datetime import datetime

def jtalk(t):
open_jtalk=[‘/usr/bin/open_jtalk’]
mech=[‘-x’,’/var/lib/mecab/dic/open-jtalk/naist-jdic’]
htsvoice=[‘-m’,’/usr/share/hts-voice/mei/mei_normal.htsvoice’]
speed=[‘-r’,’1.0′]
outwav=[‘-ow’,’/tmp/open_jtalk.wav’]
cmd=open_jtalk+mech+htsvoice+speed+outwav
c = subprocess.Popen(cmd,stdin=subprocess.PIPE)
c.stdin.write(t.encode(‘utf-8’))
c.stdin.close()
c.wait()
aplay = [‘aplay’,’-q’,’/tmp/open_jtalk.wav’]
wr = subprocess.Popen(aplay)

def say_datetime():
d = datetime.now()
text = ‘%s月%s日、%s時%s分%s秒’ % (d.month, d.day, d.hour, d.minute, d.second)
jtalk(text)

if __name__ == ‘__main__’:
say_datetime()[/python]

先ほどのファイルをimportして、jtalk.jtalk(string)で、発話させたい文章を渡せばOK。

[python]#!/usr/bin/python

#coding: utf-8
import jtalk

jtalk.jtalk(‘何か話してください’)[/python]

さあテストである。
青空文庫「注文の多い料理店」を朗読させてみる。
「新字新仮名」版をダウンロード。
http://www.aozora.gr.jp/cards/000081/card43754.html

そのままではOpen Jtalkに渡せないので、以下の変更を加える。

  • 文字コードをShift-JISからUTF-8へ
  • ルビを削除(正規表現パターンは”《.*?》”。)
  • ルビの個所を示す区切り記号を削除(”|”)
  • 構成を示す注釈を削る(正規表現パターンは”\[.*?\]”。)
  • 改行コードをCR+LFをLFのみに。(\rを削る)

できれば以下も。

  • 行頭字下げ、要するに行頭の全角スペースを削除(正規表現パターンは”^ ”。)
  • セリフのカギかっこを削る。行頭のものだけでよい。(正規表現パターンは”^「”。)

以下までできれば完璧

  • 改行をいったん全て削り、句点のあとに改行コード(\n)を挿入

neko.txtとでも保存し、以下のPythonスクリプトを実行。

[python]#!/usr/bin/python

import jtalk

for line in open(‘neko.txt’,’r’):
print(line)
jtalk.jtalk(line)[/python]

こんな感じ。

試してみるとわかるが、漢字の読み方が変だったり(山猫軒を「やまねこのき」と読んだり)、読めない漢字があったりする。
そりゃまあ仕方ないよね。

 
それはさておき。
十分なスペックがあるときに複数行の文章を読ませると、発話が終わっていないのに次の行の処理を始めてしまって困ることがある。
そういうときは発話の終了を待つよう、wait()を入れればよい。
関数jtalk()の最後に一行追加。
[python]
def jtalk(t):
open_jtalk=[‘/usr/bin/open_jtalk’]
mech=[‘-x’,’/var/lib/mecab/dic/open-jtalk/naist-jdic’]
htsvoice=[‘-m’,’/usr/share/hts-voice/mei/mei_normal.htsvoice’]
speed=[‘-r’,’1.0′]
outwav=[‘-ow’,’/tmp/open_jtalk.wav’]
cmd=open_jtalk+mech+htsvoice+speed+outwav
c = subprocess.Popen(cmd,stdin=subprocess.PIPE)
c.stdin.write(t.encode(‘utf-8’))
c.stdin.close()
c.wait()
aplay = [‘aplay’,’-q’,’/tmp/open_jtalk.wav’]
wr = subprocess.Popen(aplay)
wr.wait()
[/python]
 

参考サイト
Raspberry piで日本語音声合成(Open JTalk)を試してみる。

Linuxで音声の出力先をUSBスピーカーにする

Nano Pi Neo2にスピーカーを繋いだのだが、ノイズが入ってたまらない。
そこでUSBスピーカーから音を出すようにしてみる。
USBスピーカーはこれ。USBケーブル一本でいいので重宝している。

まずalsamixerで音量を確かめておく。

次。
aplayコマンドを使って、音声出力のハードウェアデバイスのcardとdevice番号を調べる。
—helpオプションで使い方を。

[bash]$ aplay –help
Usage: aplay [OPTION]… [FILE]…

-h, –help help
–version print current version
-l, –list-devices list all soundcards and digital audio devices
-L, –list-pcms list device names
(略)

[/bash]

-lオプションで一覧を得る。

[bash]$ aplay -l
<span class="gs">****</span> List of PLAYBACK Hardware Devices <span class="gs">****</span>
card 0: Codec <span class="p">[</span><span class="nv">H3 Audio Codec</span><span class="p">]</span>, device 0: CDC PCM Codec-0 <span class="err">[</span>]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 1: allwinnerhdmi <span class="p">[</span><span class="nv">allwinner,hdmi</span><span class="p">]</span>, device 0: 1c22800.i2s-i2s-hifi i2s-hifi-0 <span class="err">[</span>]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 2: MicroII <span class="p">[</span><span class="nv">Audio Advantage MicroII</span><span class="p">]</span>, device 0: USB Audio <span class="p">[</span><span class="nv">USB Audio</span><span class="p">]</span>
Subdevices: 1/1
Subdevice #0: subdevice #0
[/bash]
USBスピーカーだから最後のものが該当。
MicroIIということがわかる。

speaker-testコマンドでテストする。
–helpで確認するのが定石
[bash]
$ speaker-test –help

speaker-test 1.1.0

Usage: speaker-test [OPTION]…
-h,–help help
-D,–device playback device
-r,–rate stream rate in Hz
-c,–channels count of channels in stream
-f,–frequency sine wave frequency in Hz
-F,–format sample format
-b,–buffer ring buffer size in us
-p,–period period size in us
-P,–nperiods number of periods
-t,–test pink=use pink noise, sine=use sine wave, wav=WAV file
-l,–nloops specify number of loops to test, 0 = infinite
-s,–speaker single speaker test. Values 1=Left, 2=right, etc
-w,–wavfile Use the given WAV file as a test sound
-W,–wavdir Specify the directory containing WAV files
-m,–chmap Specify the channel map to override
-X,–force-frequency force frequencies outside the 30-8000hz range
-S,–scale Scale of generated test tones in percent (default=80)

Recognized sample formats are: S8 S16_LE S16_BE FLOAT_LE S32_LE S32_BE
[/bash]
-Dオプションで出力先を選ぶ。
出力先の指定はデバイスネームだからさっきのaplayコマンドで再チェック

[bash]$ aplay -L|grep MicroII
sysdefault:CARD=MicroII
Audio Advantage MicroII, USB Audio
front:CARD=MicroII,DEV=0
Audio Advantage MicroII, USB Audio
surround21:CARD=MicroII,DEV=0
Audio Advantage MicroII, USB Audio
surround40:CARD=MicroII,DEV=0
Audio Advantage MicroII, USB Audio
surround41:CARD=MicroII,DEV=0
Audio Advantage MicroII, USB Audio
surround50:CARD=MicroII,DEV=0
Audio Advantage MicroII, USB Audio
surround51:CARD=MicroII,DEV=0
Audio Advantage MicroII, USB Audio
surround71:CARD=MicroII,DEV=0
Audio Advantage MicroII, USB Audio
iec958:CARD=MicroII,DEV=0
Audio Advantage MicroII, USB Audio
dmix:CARD=MicroII,DEV=0
Audio Advantage MicroII, USB Audio
dsnoop:CARD=MicroII,DEV=0
Audio Advantage MicroII, USB Audio
hw:CARD=MicroII,DEV=0
Audio Advantage MicroII, USB Audio
plughw:CARD=MicroII,DEV=0
Audio Advantage MicroII, USB Audio

[/bash]
ということで、hw:MicroIIで試す。
[bash]$ speaker-test -Dhw:MicroII

speaker-test 1.1.0

Playback device is hw:MicroII
Stream parameters are 48000Hz, S16_LE, 1 channels
Using 16 octaves of pink noise
Channels count (1) not available for playbacks: Invalid argument
Setting of hwparams failed: Invalid argument

[/bash]
鳴らない。

この辺を読み、hwではなくplughwにしてみる。
http://www.volkerschatz.com/noise/alsa.html

[bash]$ speaker-test -Dplughw:MicroII

speaker-test 1.1.0

Playback device is plughw:MicroII
Stream parameters are 48000Hz, S16_LE, 1 channels
Using 16 octaves of pink noise
Rate set to 48000Hz (requested 48000Hz)
Buffer size range from 96 to 262144
Period size range from 48 to 131072
Using max buffer size 262144
Periods = 4
was set period_size = 65536
was set buffer_size = 262144
0 – Front Left
Time per period = 2.769702
0 – Front Left
^CTime per period = 2.426893
[/bash]
鳴った。
チャンネル2つにしてみると
[bash]$ speaker-test -c2 -Dplughw:MicroII

speaker-test 1.1.0

Playback device is plughw:MicroII
Stream parameters are 48000Hz, S16_LE, 2 channels
Using 16 octaves of pink noise
Rate set to 48000Hz (requested 48000Hz)
Buffer size range from 96 to 262144
Period size range from 48 to 131072
Using max buffer size 262144
Periods = 4
was set period_size = 65536
was set buffer_size = 262144
0 – Front Left
1 – Front Right
^CTime per period = 3.587277

[/bash]
左右で鳴る。
さらにaplayで試す。
[bash]$ aplay /usr/share/sounds/alsa/Front_Center.wav -D plughw:MicroII
Playing WAVE ‘/usr/share/sounds/alsa/Front_Center.wav’ : Signed 16 bit Little Endian, Rate 48000 Hz, Mono

[/bash]
よさそう。
これでmpg123などを使えば、mp3ファイルなども鳴らすことができる。
mpg123でデバイスを指定するには-aオプションで。

[bash]mpg123 -q -a plughw:MicroII XXXX.mp3

[/bash]
ためしにデバイスにdefaultを指定すると文字通り標準設定のデバイスから出力を試みる。
USBスピーカーはならないはず。

[bash]$ aplay /usr/share/sounds/alsa/Front_Center.wav -D default

[/bash]
デフォルトの出力先を変えるには、以下の手順に沿えばよい。
https://www.alsa-project.org/main/index.php/Setting_the_default_device

 

[UNIX tips] 復活祭(イースター)の日付に神代の遊び心を見た(2014年のイースターは4/20です)

cal/ncalにはイースター(復活祭)を表示するオプションがある。
私はキリスト教徒ではないのですが、コマンドにこういったオプションが自然に入っていることに感銘したので記録する。

結論

ncal -e (西方教会向け)
ncal -o (東方教会向け)

* Linux(GNU cal)ならcal -e/cal -oでOK。

calコマンド

UNIXにはcalコマンドという、カレンダーを表示するコマンドがある。
手許のMacにもきちんと入っていて(MacなのでFreeBSD由来です)、実行してみると以下のように表示される。

$ cal

2月 2014
日 月 火 水 木 金 土
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28

 

復活祭とは

wikiがこちら。
http://ja.wikipedia.org/wiki/復活祭

「復活祭は基本的に「春分の日の後の最初の満月の次の日曜日」に祝われるため、年によって日付が変わる移動祝日である。」ということなので、コンピュータに計算させるニーズは、それなりにあるというわけですな。

コマンド実行例

OS XやFreeBSDで復活祭の日付を表示させるには、calコマンドではできず、ncalコマンドを使う必要がある。
GNU calなら問題なし、つまり大半のLinuxでも問題なし。
ncalコマンドは、calコマンドにさらにレイアウトやオプションを追加したもの。

-e、つまりeaster、で復活祭の日付を表示する。
-o、つまりOrthodox(正教)での復活祭の日付を表示する。

$ ncal -e
4月 20 2014

$ ncal -o
4月 20 2014

東方、西方、同じようですな。

おわりに

マニュアルを見ると、calコマンドはAT&T UNIXのVersion 5で現れたとのこと。
1975年ごろ。
まさに神代である。

そして、そんな時代でターミナル上にカレンダーを豪快に表示させるという、ロックなコマンドが生まれてるんである。
「真面目にやれよ」とか怒られなかったんですかね。
そしていつごろか、そこに復活祭のような移動祝祭日を計算するオプションが入ってくる。
のびのびしているというか、融通無碍というか。
気持ちよくて、駄文を記した次第でございます。

zfs snapshotの差分send/recvについて

 

zfs snapshotの差分send/recvについて

zfsのバックアップは、snapshotを撮っておいて、自ホストの別zpool、あるいは他ホストの別zpoolに移すことで行われる。
バックアップは定期的に行われるものであるが、ではそのsnapshotを移す際、毎回毎回まるまる送っていたんでは帯域も時間もディスク寿命も無駄である。
zfsはそこも考慮していて、差分だけを送ればいいようにincrementalオプションがきちんと用意されている。

書式は以下の通りsendに-iオプションを与えるだけ。
(recv側にはsnapshotAがすでに転送済みの前提)

zfs send -i <snapshotA> <snapshotB> | zfs recv <target pool>

 

よろしい。では次回からは…?

ふむ。
では二回目以降はどうするのだろう?
上記の書式例でsnapshotCが出来たらどうするのだろう?
まさかsnapshotA, B, Cを全部並べるのか?と思ったけどそれは間違いで、snapshotB, Cだけでよい。

recv側にはsnapshotAがすでにあるとき。
zfs send -i snapshotA snapshotBでAとBの差分のみが送られる。
つぎにzfs send -i snapshotB snapshotCでBとCの差分のみが送られる、というわけ。

では早速試してみる。が。

「転送先が更新されているため差分を転送できません」

こんなエラーがでる。

cannot receive incremental stream: destination warehouse/dir has been modified
since most recent snapshot

まあ文字通りなんですが。
バックアップ用の転送先が更新されるのはなぜか分からないが(atimeだろうか?)。

こういう場合には、recv側で-Fオプションを与えればよい。
-Fオプションによりrecv側は最新snapshotに強制rollbackして、それからsnapshotを受け取る。

下記の例だと、recv側は@20131226にいったんrollbackしてから受け取る。

$ sudo sh -c "zfs send -i vault/chamber@20131226 vault/chamber@2014010
2 | zfs recv -F warehouse/chamber"

 

atimeのoff

recv側がなぜ更新されてるのか、atimeが怪しいのでoffにしておく。
recv側のzpoolに対してzfs set atime=offするだけ。
(zpoolから切り出されたzfsすべてに適用される)

$ zfs get atime warehouse
NAME       PROPERTY  VALUE  SOURCE
warehouse  atime     on     default
$ sudo zfs set atime=off warehouse
$ zfs get atime warehouse
NAME       PROPERTY  VALUE  SOURCE
warehouse  atime     off    local
$ zfs get atime warehouse/chamber
NAME               PROPERTY  VALUE  SOURCE
warehouse/chamber  atime     off    inherited from warehouse

 

Windows7でsambaのファイルがうまく開けない件について(oplocks死すべし)

昨年あたりからWindows XPのサポート終了にともない、Windows 7へクライアントOSを移す企業が多い。
私の仕事PCも過日、ついに7に変更された。
さすがに色んなところが改善されていて使いやすくなっているのだが、なぜかSambaに置いてあるファイルが壊れたり、誰も開いていないはずなのに読み取り専用でしか開けない事象が頻発した。
特に共有設定したOfficeドキュメントで顕著。

しばらく悩んでいたのだが、Sambaのログにoplocks failedという記録を見つけて(おそらく)対処方法が分かった。
以下に示す。
Sambaのバージョンは3.6.22である。

結論

smb.confのglobalセクションに以下を書け。

oplocks = No
blocking locks = No

 

ネタ元

ネットを渉猟したところ、やはりOplocksの評判はあまりよくない。

以下のホワイトペーパーが分かりやすかった。
Oplocksとread cachingが原因と断定している。

Opportunistic Locking and Read Caching on Microsoft Windows Networks
http://www.dataaccess.com/whitepapers/opportunlockingreadcaching.html

先に進む前にプロトコルとしてのSMBでいくつか確認を。

 

SMB(プロトコル)のバージョンについて

Windowsファイル共有で使われるプロトコルSMBは、Vistaでバージョン2, 7でバージョン2.1になった。
Oplocksとread cachingもバージョン2以降で追加、あるいは強化されている。
詳細は以下。

SMB の新機能
http://technet.microsoft.com/ja-jp/library/ff625695.aspx
SMB1⇒2⇒2.1への変化と新機能
http://blog.goo.ne.jp/mito_and_tanu/e/10c47629fbb7e7d3d73cbd54a1a9f28d


Oplocksとは

ネットワークの効率化を狙う、Windows固有の機能。
複数のプロセスが同じファイルをロックでき、なおかつクライアントがデータをキャッシュできる。

Chapter 17. File and Record Locking
http://www.samba.org/samba/docs/man/Samba-HOWTO-Collection/locking.html
http://www.samba.gr.jp/project/translation/Samba3-HOWTO/locking.html

Read Cachingとは

Oplocksの一機能。クライアント側でのデータキャッシュ。
クライアントがローカルでデータをキャッシュする目的は、ネットワーク越しの書き込み回数を減らすこと、ひいてはネットワークの効率化のため。
ファイルの同じ部分(the same region)に対する書き込みをまとめて、一回で済ませる。

Oplocksの危険性

Oplocksが狙い通りに動けば、目的通りネットワークの効率化が図られる。
問題なのはネットワークに何か問題が起こり、キャッシュが適切にフラッシュされない場合。ともするとファイルの破壊を引き起こす。
とくにデータベースが危険。
以下のリンクに詳細が。
本記事の末尾に私訳を付す。

Locks and Oplocks
http://www.samba.org/samba/docs/using_samba/ch08.html

対策:Oplocksを無効にしろ

ここまで分かれば、対策は簡単。
Oplocksを無効にすればよい。
無効にする方法には、サーバ側での方法、クライアント側での方法がある。
クライアントと言っても、たいていはWindowsであろうが、Windowsで対処しようとするとレジストリの変更が必要になる。
さすがにそれは面倒だ。
そこでサーバ側、Sambaの設定でOplocksを無効にする。

smb.confでOplocksを無効にする

oplocks = noとすればよい。
デフォルト無効にしたければ[global]セクションに、共有ごとに無効にしたければ共有のところに書けばよい。
smb.confのmanは一見、共有ごとにしか指定できないように見えるかもしれないが[global]にも使える。
また、oplocksはbooleanなのでnoでもfalseでも、はたまた0でも理解してもらえる。

なお、ファイルごとに有効無効を指定したければveto oplock filesで指定できる。

smb.confのmanより抜粋

oplocks (S)

This boolean option tells smbd whether to issue oplocks
(opportunistic locks) to file open requests on this share.
(略)

Default: oplocks = yes

smb.confのmanよりPARAMETERS抜粋

PARAMETERS
(略)
The letter G in parentheses indicates that a
parameter is specific to the [global] section. The letter S indicates
that a parameter can be specified in a service specific section. All S
parameters can also be specified in the [global] section - in which
case they will define the default behavior for all services.

 

blocking locks

Sambaはロックの方法を指定できる。
blocking locksというものである。
これは、ファイルをロックするときに、特定部分だけをロック(range lock)するか、ファイル全体をロックするか、というものである。
デフォルトで有効。
WindowsのRead Cachingに対応してそうな機能である。
加えてmanを見ると、「range lockに失敗すると、タイムアウトするまで何回かrange lockを再試行する」などと書いてある。
これも無効にしよう。

blocking locks (S)

This parameter controls the behavior of smbd(8) when given a
request by a client to obtain a byte range lock on a region of an
open file, and the request has a time limit associated with it.

If this parameter is set and the lock range requested cannot be
immediately satisfied, samba will internally queue the lock
request, and periodically attempt to obtain the lock until the
timeout period expires.

If this parameter is set to no, then samba will behave as previous
versions of Samba would and will fail the lock request immediately
if the lock range cannot be obtained.

Default: blocking locks = yes

 

設定例

以上を踏まえて、以下のように設定
しょぼい英文は勘弁しておくれ。

[global]
(略)
#
# Disable oplock
# It's supposed to improve network performance but causes problems
#
# See:
# Opportunistic Locking and Read Caching on Microsoft Windows Networks
# http://www.dataaccess.com/whitepapers/opportunlockingreadcaching.html
#
oplocks = no
blocking locks = no

 

再起動しておしまい。

$ sudo service samba restart
Performing sanity check on Samba configuration: OK
Stopping winbindd.
Waiting for PIDS: 667.
Stopping smbd.
Waiting for PIDS: 664, 664.
Stopping nmbd.
Waiting for PIDS: 661.
Removing stale Samba tdb files: .. done
Starting nmbd.
Starting smbd.
Starting winbindd.

 

(参考)Locks and Oplocks 私訳

http://www.samba.org/samba/docs/using_samba/ch08.html
Locks and Oplocks

oplocksの使用によって得られるパフォーマンス増は、大変に望ましいことがほとんどだ。
しかしながら、クライアントやネットワークのハードウェアの信頼性が怪しいような状況では、クライアントにデータをキャッシュさせることは大きなリスクになり得る。
クライアントが書き込みのためにファイルを開き、oplockを行う場合を考えてみよう。
そして他のクライアントが同じファイルを開こうとしたとき、”oplock break”要求が最初のクライアントに送られる。
もしこの要求が何らかの理由により失敗し、二番目のクライアントがファイルの書き込みをしたとする。
二つのプロセスが同時に書き込みをするわけだから、ファイルは簡単に壊れてしまうだろう。
残念なことに上記のケースは現実に起こりうる。
SMBネットワークにおける複数のWindowsクライアント環境では、このようなおかしな動きは頻繁にみられる。
たくさんのクライアントが同時に書き込みを行うデータベースファイルは特に影響を受けてしまう。

oplocksの失敗するもっと具体的な例を挙げると、それはデータベースファイルが大変に大きい場合である。
クライアントがこのようなファイルを開き、oplockを許可されると、非常に大きな遅延が発生する。
その一部を修正するためだけであっても、クライアントがファイル全部をキャッシュするからである。
このファイルを別のクライアントが開こうとしたとき、状況はさらに悪くなる。
二番目のクライアントがファイルを開くためには、最初のクライアントがキャッシュをすべてサーバに書き戻す必要があるからだ。
このために、また別の遅延が発生する(しかも双方のクライアントで)。
結果として、タイムアウトにより二番目のクライアントがファイルオープンに失敗し、たぶんデータが壊れた旨のWarning messageも併せて表示されるだろう。

In most cases, the extra performance resulting from the use of oplocks is highly desirable. However, allowing the client to cache data can be a big risk if either the client or network hardware are unreliable. Suppose a client opens a file for writing, creating an oplock on it. When another client also tries to open the file, an oplock break request is sent to the first client. If this request goes unfulfilled for any reason and the second client starts writing to the file, the file can be easily corrupted as a result of the two processes writing to it concurrently. Unfortunately, this scenario is very real. Uncoordinated behavior such as this has been observed many times among Windows clients in SMB networks (with files served by Windows NT/2000 or Samba). Typically, the affected files are database files, which multiple clients open concurrently for writing.

A more concrete example of oplock failure occurs when database files are very large. If a client is allowed to oplock this kind of file, there can be a huge delay while the client copies the entire file from the server to cache it, even though it might need to update only one record. The situation goes from bad to worse when another client tries to open the oplocked file. The first client might need to write the entire file back to the server before the second client’s file open request can succeed. This results in another huge delay (for both clients), which in practice often results in a failed open due to a timeout on the second client, perhaps along with a message warning of possible database corruption!

Hadoopフレンドリーなデータとは

 

ログは1行1イベントに

何を言っとるんだお前はという感じであるが、つまりこういう事である。

通常、ログは1行が1イベントである。
たとえばapacheのログやらなにやら、みんなそうである。
少なくともUNIX系のシステムであれば、これは常識である。
しかし、この世の中、UNIX系の常識が通用しないログだって山ほどあるのである。

1イベントが複数行にわたるログがなぜいけないか。

たとえば以下の様な擬似ログを考えてみよう。

まったく関係のない話だが、以下のデータは手元にあるLIFEのノートから適当にでっち上げた。
冒頭にタイムスタンプがあり、イベントの内容が記される、典型的なログである。

2013/12/16 17:55:05.6582 N28 [499016804864] Noble note section B6 5m/m

しかしこれが、以下のように複数行にわたって記録(あるいは表示)される、こういうログを相手にすることだってあるのだ。

2013/12/16 17:55:05.6582 N28
4990 1680
4864
Noble note section
B6 5m/m

 

これは主に可読性を意識したせいであろうが、こういったログをHadoopで扱うのは難しい。

Hadoopのデータの扱い方

というのも、Hadoopは分散処理のためにログを分割するからだ(デフォルトでは64MBごと)。

しかもその分割は、単純にサイズのみで判断され、文脈は考慮されない。
上のログで言えば、Nobleの手前で切られてしまってログとして意味がなくなってしまうことだってある。

これを避けるには、Hadoopにファイルそのものではなくて、ファイルリストを与える手があるけれども、それではHadoopの長所を活かせない。
ファイルリストは綺麗に分割されるけど、ファイルの大きさはまちまちだから。

ログの整形

というわけで、こういったログを扱う前に、下準備として1行1イベントにまとめてしまおう。

まとめ自体もHadoopで処理してしまえば楽である。
Hadoopの象本Appendix Cに良い例があるのでこれを使う。
ここでfiles.txtは処理するログファイルをリストしたものとする。
また、concat.shは1行1イベントにまとめるスクリプトとする。

$ hadoop jar $HADOOP_INSTALL/contrib/streaming/hadoop-*-streaming.jar \
-D mapred.reduce.tasks=0 \
-D mapred.map.tasks.speculative.execution=false \
-D mapred.task.timeout=12000000 \
-input files.txt \
-inputformat org.apache.hadoop.mapred.lib.NLineInputFormat \
-output output \
-mapper concat.sh \
-file concat.sh

 

reduceは必要ないのでmapred.reduce.tasks=0。
重複して書き込んでほしくないのでmapred.map.tasks.speculative.execution=false。
タイムアウトは長めに。
mapが一回に処理するファイルはひとつにしたいので、-inputformat org.apache.hadoop.mapred.lib.NLineInputFormat 。

以上

hadoopのsafemodeとfsck

 

Hadoopのnamenode兼datanodeが電源瞬断のせいで壊れてしもうた。
勉強用のHadoopなのでダメージはないのだが、せっかくなのでfsckなどを試した結果を記す。
hadoop-1.0.0。
OS側でもfsckはしておいた。

サマリ

  • HDFSにもfsckがある。
  • hadoop fsckでチェック、修正できる。
  • hadoop fsckと叩けば使い方の簡単な説明が表示される。
  • namenodeがsafemodeのためにHDFSが読み取り専用になっていることがある。
  • 読み取り専用ではfsckで修正できないので、safemodeから出る必要がある。

 

HDFSでのfsck

hadoop fsck <path>と指定すればよい。
CORRUPT!とのことで、壊れておりますなあ。

$ hadoop fsck /
FSCK started by hadoop from /172.29.17.159 for path / at Wed Dec 25 18:51:37 JST 2013
(略)
/var/hadoop/mapred/system/jobtracker.info: MISSING 1 blocks of total size 4 B.Status: CORRUPT
Total size: 45638364128 B
Total dirs: 5321
Total files: 13059
Total blocks (validated): 9819 (avg. block size 4647964 B)
********************************
CORRUPT FILES: 2470
MISSING BLOCKS: 2498
MISSING SIZE: 15590981155 B
CORRUPT BLOCKS: 2498
********************************
Minimally replicated blocks: 7321 (74.559525 %)
Over-replicated blocks: 0 (0.0 %)
Under-replicated blocks: 7321 (74.559525 %)
Mis-replicated blocks: 0 (0.0 %)
Default replication factor: 2
Average block replication: 0.7455953
Corrupt blocks: 2498
Missing replicas: 7321 (100.0 %)
Number of data-nodes: 1
Number of racks: 1
FSCK ended at Wed Dec 25 18:51:38 JST 2013 in 1185 milliseconds

The filesystem under path '/' is CORRUPT

 

壊れている場合の対処:消去か移動

壊れている場合には対処が二つ。
壊れているブロックを消すか、lost+foundに移すか。
消す場合には-delete, 移す場合には-moveを指定する。
以下は消した場合の例….なのだが、namenodeがsafemodeにいるので変更が出来ないとのこと。

$ hadoop fsck / -delete
FSCK started by hadoop from /172.29.17.159 for path / at Wed Dec 25 18:58:29 JST 2013
.
/tmp/hadoop-hadoop/mapred/staging/hadoop/.staging/job_201308300823_0001/job.jar: CORRUPT block blk_3947350403157044322

/tmp/hadoop-hadoop/mapred/staging/hadoop/.staging/job_201308300823_0001/job.jar: MISSING 1 blocks of total size 66249 B.FSCK ended at Wed Dec 25 08:58:29 JST 2013 in 5 milliseconds
Cannot delete /tmp/hadoop-hadoop/mapred/staging/hadoop/.staging/job_201308300823_0001/job.jar. Name node is in safe mode.
The ratio of reported blocks 0.7456 has not reached the threshold 0.9990. Safe mode will be turned off automatically.

Fsck on path '/' FAILED

 

safemodeとは

そもそもnameodeは通常の動作として、起動時には状態がsafemodeである。
safenodeのまま待機をしているうちに、datanodeが起動し、保管しているブロックの報告をnamenodeに行う。
namenodeは、充分なブロックの確認ができれば自動的にsafemodeを出る。
「充分なブロック」がどれくらいか、は設定できる。

safemodeのときは読み取り専用になり、複製や消去もできないが、手動でsafemodeに入ったり出たりすることができる。

以下、公式からの引用

Safe mode is entered automatically at Namenode startup, and leaves safe mode automatically when the configured minimum percentage of blocks satisfies the minimum replication condition. Safe mode can also be entered manually, but then it can only be turned off manually as well.
Safe mode maintenance command. Safe mode is a Namenode state in which it
1. does not accept changes to the name space (read-only)
2. does not replicate or delete blocks.

今回、問題の発生したnamenodeはdatanodeも兼ねている。
電源瞬断でブロックが壊れているからブロックの報告はできないし、そもそもnamenodeが保管しているメタデータも壊れている。
ボロボロである。
したがって、いつまで待ってもsafemodeから出るはずがない。
そこで手動でsafemodeを解除する。

safemodeの操作

safemodeの操作はhadoop dfsadmin -safemodeに続けて行う。
getで状態を得る。
enterでsafemodeに入る。
leaveでsafemodeから出る。
面白いのは、wait。
safemodeから出たらコマンド実行する、というもの。

# 状態を得る。
$ hadoop dfsadmin -safemode get

# 終わってからコマンド実行
$ hadoop dfsadmin -safemode wait

# safemodeに入る。
$ hadoop dfsadmin -safemode enter

# safemodeから出る。
$ hadoop dfsadmin -safemode leave

以下、実際の例。
safe modeがONになっているので、OFFに。

$ hadoop dfsadmin -safemode get
Safe mode is ON

$ hadoop dfsadmin -safemode leave
Safe mode is OFF

改めてfsck / -delete
blockが2000個くらい消えた。ま、まあ勉強用だし(震え声)

$ hadoop fsck / -delete
FSCK started by hadoop from /172.29.17.159 for path / at Wed Dec 25 19:05:54 JST 2013
(略)
.......Status: HEALTHY
Total size: 29928657533 B
Total dirs: 5321
Total files: 10589
Total blocks (validated): 7319 (avg. block size 4089173 B)
Minimally replicated blocks: 7319 (100.0 %)
Over-replicated blocks: 0 (0.0 %)
Under-replicated blocks: 7319 (100.0 %)
Mis-replicated blocks: 0 (0.0 %)
Default replication factor: 2
Average block replication: 1.0
Corrupt blocks: 0
Missing replicas: 7319 (100.0 %)
Number of data-nodes: 1
Number of racks: 1
FSCK ended at Wed Dec 25 19:05:55 JST 2013 in 1696 milliseconds

The filesystem under path '/' is HEALTHY

HEALTYになったので、これでhadoopが使える状態に戻った。
以上。

[メモ] grepでコメント行、空行以外の行を表示させるには

 

いつも忘れるのでメモ。
あるファイルから、コメント行と空行を除いて表示させるには。

コメント行なら”^ *#”がマッチ。
空行なら^ *$”がマッチ。

egrepに -v “^ *#|^ *$” を与えればよい。
冒頭タブとかは漏れてしまうけど、ひとまずはこれでいいかな。

以下、実行例。

$ egrep -v "^ *#|^ *$" ./nginx.conf
user  www;
worker_processes  1;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   /usr/local/www/nginx;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/local/www/nginx-dist;
        }
    }
}

 

Pythonアプリに引数を渡そう

PythonでWindowsアプリケーションを作ったとする。
たとえばファイルを引数に取り、整形するものだとする。
せっかくWindowsアプリケーション、というかGUI上のプログラムなんだから、アプリケーションにドラッグ&ドロップしたいじゃないか。
どうしたらできるか。
…まあ普通にargvで受け取ればいいんですがね。
以下、Pythonにおけるargvの使い方メモ。

sys.argv

sys.argvは、コマンドラインで渡される引数が入ったリスト。
sys.argv[0]にはスクリプト自身が入る。
len(sys.argv)で引数の数をカウントできる。
できるが、スクリプト自身も含めての数であることに注意。sys.argv[0]に自分が入っているからね。

サンプルスクリプト

サンプルスクリプトである。

import sys

print "the name of script is :", sys.argv[0]
print "the number of arg is :", len(sys.argv)

for arg in sys.argv:
    print arg

この実行結果は、たとえば以下のようになる。

$ python ./argv.py a b c
the name of script is : ./argv.py
the number of arg is : 4
./argv.py
a
b
c

sys.argvはリストなのでforなどで中身を一つ一つ参照できるが、自分自身からまず始まるのは美しくない。

引数一つ目から扱うには

以下のようにする。

import sys

def main(argv):

    print "the name of script is :", sys.argv[0]
    print "the number of original arg is :", len(sys.argv)
    print "the number of altered arg is :", len(argv)

    for arg in argv:
        print arg

if __name__ == "__main__":
    main(sys.argv[1:])

端的に言えば、一枚レイヤを入れる。
一番下のif文部分でsys.argvを受けたら、リストの一つ目以降、つまりスクリプト自身を除いた残りをmain()に渡すというわけ。
出力は例えば以下のようになる。

$ python ./argv.py a b c
the name of script is : ./argv.py
the number of original arg is : 4
the number of altered arg is : 3
a
b
c

ベリーナイス。

シャレオツな使い方(引数の数を担保したい)

さて。もうちょっと頭のいいことをやってみたい。
具体的には、引数を一つだけ取るようにしたい。
さてサンプル。

def main(argv):

    print "the name of script is :", sys.argv[0]
    print "the number of original arg is :", len(sys.argv)
    print "the number of altered arg is :", len(argv)

    if len(argv) != 1:
        sys.exit("Usage: %s <filename>" % sys.argv[0])

    print argv[0]

if __name__ == "__main__":
    main(sys.argv[1:])

出力例

$ python ./argv.py a b c
the name of script is : ./argv.py
the number of original arg is : 4
the number of altered arg is : 3
Usage: ./argv.py <filename>

 

シャレオツな使い方(引数の内容を担保したい)

さらに。
ファイルを引数に取るとして(さあ冒頭の話につながってまいりました!)、そのファイルの有無を確認したい。
サンプル。

import sys
import os

def main(argv):

    print "the name of script is :", sys.argv[0]
    print "the number of original arg is :", len(sys.argv)
    print "the number of altered arg is :", len(argv)

    if len(argv) != 1:
        sys.exit("Usage: %s <filename>" % sys.argv[0])
    if not os.path.exists(argv[0]):
        sys.exit('ERROR: file %s was not found!' % argv[0])

    print argv[0]

if __name__ == "__main__":
    main(sys.argv[1:])

出力例

$ python ./argv.py a
the name of script is : ./argv.py
the original number of arg is : 2
the altered number of arg is : 1
ERROR: file a was not found!

以上でござる

[メモ]zpoolのVersionについて(Versionナンバはもう使われない)

 

過日、zpool v28のアップグレードをして、いくつかのfeature flagsを有効にした。
したのだが、よく分からないことがいくつかあった。

すなわち、最新のはずのv28なのにアップグレードを促されたこと、アップグレードしたら zpool get allの結果からバージョンが消えてしまったこと、である。

WikipediaのZFSやOpenZFSを眺めてみて分かったのは、zpool((Open)ZFS)はv28以降、バージョンナンバの使用を止めてしまったせいとの由。
http://en.wikipedia.org/wiki/OpenZFS
http://en.wikipedia.org/wiki/ZFS#RELEASEHISTORY

以下に調べた結果を示す。
なお、ご覧の通り情報の出所はwikipediaである。
問題ないとは思うが、各位においてはご留意されたい。

ZFSの歴史

もともと、zpoolやZFS filesytemに機能が追加されるたび、バージョンナンバがインクリメントされてきた。
しかし2010年、OpenSoralisの開発が停止され、ZFSはオープンソースではなくなった。
本家(Oracle)のZFSはプロプライエタリとなって、2013/12/24現在、ZFS filesystemはv6, ZFS Poolはv34まで進んでいる。

それはそれとして、illumos(OpenSolaris後継)やBSD/Linux/OSX関連の方々は、オープンであったzfs v5, zpool v28をベースに本家とは別の開発が進める。
これが2013年にOpenZFSとなる。

バージョンナンバとフィーチャーフラグ

OpenZFSはいろんな所でいろんな人々が開発を続けている。
こういった状態で、単一のバージョンナンバを付与することは不可能である。
そこでフィーチャーフラグ(feature flags)という考え方を導入する。
機能ごとに個別の名前を与える、というもの。

個別の名前は、feature@<org-name>:<feature-name>という書式で表される。
org-nameは当該機能を開発した組織名。一意の名前。もちろん、勝手につけることは許されない。
org-nameが明らかな場合には、feature@<feature-name>と短縮してもよい。
feature@async_destroyというように。

フィーチャーの状態

フィーチャーの状態には三つある。
disabled, enabled, activeである。

  • disabled: 無効。機能を使えない状態。
  • enabled: 機能を使える状態。有効にはなっていない。
  • active: 機能が有効。旧バージョンとの互換性がなくなっている。

フィーチャーの種類

  • features for write : 書き込みには必須の機能。読み込みのみであれば不要。
  • features for read : 読み込み、あるいはpoolを開くために必須の機能。

たとえばasync_destroyはデータ書き込みの時だけ必要な機能なのでfeatures for write。

バージョンについて

一つでもフィーチャーを有効にするとpoolのバージョンは1000になる。
1000とはめちゃくちゃな数に見えるかもしれないが、本家のバージョンと被らないようにするため。

おわりに

ここで改めてzpoolの出力を見てみると、poolのバージョンがハイフンになっていることや、フィーチャーの記載など、なるほどと理解できることがありますな。

$ zpool get all|grep vault
vault size 2.27T -
vault capacity 64% -
vault health ONLINE -
vault version - default
(略)
vault feature@async_destroy enabled local
vault feature@empty_bpobj enabled local
vault feature@lz4_compress enabled local