のんびりしているエンジニアの日記

ソフトウェアなどのエンジニア的な何かを書きます。

WebDNNのサンプルとコードの解説に挑戦

皆さんこんにちは。
お元気ですか。師走の12月らしく締め切りに追われています。

この記事は「Deep Learning フレームワークざっくり紹介 Advent Calendar 2017」第7日目です。

qiita.com

DeepLearningフレームワークの1つ、WebDNNのご紹介をします。
このフレームワークを調べたきっかけとして、「MakeGirls.moe」があります。
MakeGirls.moeは萌画像生成のWebサービスで、WebDNNを使用して提供しているようです。

make.girls.moe

WebDNN

WebDNNは東京大学 原田・牛久研究室が作成した深層学習モデル(DNN)を
ウェブブラウザ上で高速実行するためのオープンソースフレームワークです。

次のサイトのアワードに掲載されるほど、良いソフトウェアでもあります。

Open source software Award 2017 for WebDNN at ACM Multimedia 2017

DNNは顕著な成果を様々なタスクであげていますが、度々、計算コストが問題になります。
サーバ上でDNNの全ての計算を行う場合、様々なユーザが送信するリクエストは多数あります。
これを処理しきるにはそれなりのスペックのマシン及び台数が求められます。(多分、個人では無理)

WebDNNはウェブブラウザ上での実行を前提とし、様々な計算の最適化がされているようです。

github.com

WebDNNでWebサイトを構築するまでの手順

WebDNNはいくつかの有名なフレームワークに対応しています。
公式で確認ができているのはChainer, Kerasでしょうか。

KerasからWebDNN用のオブジェクトに変換してそれを利用し、Webサイトで動かすまでやってみます。

KerasからWebDNNへ変換する

まずは、Pretrainingで有名なResNet50を取得します。
KerasからResNet50を取得し、まずはディスクに保存します。
下記の実行は「example/resnet/」配下で行ってください。
(短いのでインタプリタでの実行で良いと思います)

from keras.applications import resnet50
model = resnet50.ResNet50(include_top=True, weights='imagenet')
model.save("resnet50.h5")

次にWebDNNで実行するモデル形式へ変換します。実行コマンドは次の通りです。

python ../../bin/convert_keras.py resnet50.h5 --input_shape '(1,224,224,3)' --out output_keras

Kerasを使い、Web上で実行する。

さて、ここからモデルを画面上で使います。
サンプルのサーバを立ち上げてみます。
サーバを起動します。setup.pyと同じ箇所で次のコマンドを実行します。

python -m http.server

実行後に次のURLにアクセスします。
(README.mdと異なることに注意、パスはexample配下をチェックして適切なパスを使いましょう)

http://localhost:8000/example/resnet/

アクセス後に次の画面が出てきます。
この画面でRunボタンを押すことで、WebDNNで変換されたモデルの実行が可能です。

f:id:tereka:20171203002625p:plain

さて、ここから先はWebDNNのjavascriptを見てみます。

ソースコード

Python(bin/convert_keras.py)

Kerasによる変換の解説になります。
まずは、KerasConverterを使います。このKerasConverterにより、
入力したモデルをWebDNN IR Graphへと変換します。

そして、この変換したWebDNN IR Graphとバックエンド(webgpu,webgl,webassembly,fallback)
とencodingを入力して与えると変換されたデータの出力を行います。
WebDNNのREADME曰く「--encoding eightbit」と引数に与えるとモデルが1/5サイズになるとのこと。

    backends = args.backend.split(",")
    for i, backend in enumerate(backends):
        console.stderr(f"[{path.basename(__file__)}] BackendName: {console.colorize(backend, console.Color.Cyan)}")
        try:
            graph_exec_data = generate_descriptor(backend, graph, constant_encoder_name=args.encoding)
            graph_exec_data.save(output_dir)
        except Exception as ex:
            if flags.DEBUG:
                raise ex

Javascript

ヘッダー

まずは、index.htmlを確認します。
ヘッダーとしてインポートされているスクリプトで必要なのは、
「../../dist/webdnn.js」と「script.js」です。
独自で開発する場合には「../../dist/webdnn.js」をインポートする必要があります。

<head>
    <title>ResNet-50 conversion WebDNN example</title>
    <meta charset="utf-8">
    <script src="../../lib/inflate.min.js"></script>
    <script src="../../dist/webdnn.js"></script>
    <script src="../data/imagenet_labels.js"></script>
    <script src="script.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css">
</head>
scrpit.js

まず、実行時には次のコードが実行されます。
ここではモデルと画像を読み込み計算結果を返します。

prepare_runでモデルをファイルから読み込むような挙動をしています。
次にrunner#getInputViewsで計算する画像をセットします。

最後にrunner.run();で予測をする計算を行い、getOutputViewsで計算結果を取り出しています。
Chainerとその他でOrderを変化させているのは、
フレームワークによってCHW(Channel, Height,Width)とHWCの順番が異なることが理由です。

async function run() {
    let runner = await prepare_run();

    runner.getInputViews()[0].set(await WebDNN.Image.getImageArray(document.getElementById('input_image'), {
        order: getFrameworkName() === 'chainer' ? WebDNN.Image.Order.CHW : WebDNN.Image.Order.HWC,
        color: WebDNN.Image.Color.BGR,
        bias: [123.68, 116.779, 103.939]
    }));

    let start = performance.now();
    await runner.run();
    let elapsed_time = performance.now() - start;

    let out_vec = runner.getOutputViews()[0].toActual();
    let top_labels = WebDNN.Math.argmax(out_vec, 5);

    let predicted_str = 'Predicted:';
    for (let j = 0; j < top_labels.length; j++) {
        predicted_str += ` ${top_labels[j]}(${imagenet_labels[top_labels[j]]})`;
    }
    log(predicted_str);

    console.log('output vector: ', out_vec);
    log(`Total Elapsed Time[ms/image]: ${elapsed_time.toFixed(2)}`);
}

prepare_runでは、モデルをロードします。
backendとframeworkの名前で識別できるので、その名前を指定して読み込みます。
下記のコードでは非同期にモデルを読み込み、
既にモデルが存在する場合にはその読み込まれているモデルを返します。

async function prepare_run() {
    let backend_name = document.querySelector('input[name=backend]:checked').value;
    let framework_name = getFrameworkName();
    let backend_key = backend_name + framework_name;
    if (!(backend_key in runners)) {
        log('Initializing and loading model');
        let runner = await WebDNN.load(`./output_${framework_name}`, {backendOrder: backend_name});
        log(`Loaded backend: ${runner.backendName}, model converted from ${framework_name}`);

        runners[backend_key] = runner;
    } else {
        log('Model is already loaded');
    }
    return runners[backend_key];
}

最後にloadImageを紹介します。getImageArrayを使うことで、画像をurl(path)から読み込み、
画像データとして保持します。これによりCanvasに画像を埋め込みます。

async function loadImage() {
    let imageData = await WebDNN.Image.getImageArray(document.getElementById("image_url").value, {dstW: 224, dstH: 224});
    WebDNN.Image.setImageArrayToCanvas(imageData, 224, 224, document.getElementById('input_image'));

    document.getElementById('run_button').disabled = false;
    log('Image loaded to canvas');
}

最後に

これを使うとWebページでjavascriptを使い、モデルの実行ができます。
サーバ上で動作させるよりもユーザに提供しやすいので非常におすすめです。
これを使えばユーザにモデルの提供がしやすくなり、遊びで作ったツールを試してもらうことができます。

いつか、提供しよう。そうしよう

Pythonで自然言語処理のタスクをやってみる。

皆さんこんにちは
お元気ですか。アドベントカレンダー真っ盛りですね。
本日は「python Advent Calendar 2017」のアドベントカレンダー第5日です。

qiita.com

自然言語処理には様々なライブラリ(NLTKやCoreNLP)があります。
せっかくの機会として、本記事では紹介が少ないspaCyを紹介します。

spaCy

spaCyとは

spaCyはPythonの発展的な自然言語処理のライブラリです。
実際に使われていることを想定しており、英語、ドイツ語、フランス語、スペイン語に対応しています。
トークナイザーは日本語もあるとのこと(確かJanomeで動作します)。

github.com

次のリンク先には他の自然言語処理ライブラリの
アルゴリズムの観点や精度(Dependency parsing、Named entity comparison)が
載っており、他のライブラリとどのような点が違うのかを確認できます。

spacy.io

インストール

pip install spacy

spaCyで遊んでみる

spaCyですが、日本語対応が用意されています。
ただ、残念ながら日本語に対応したモデルがないため、
まだ本格的な日本語解析は難しいのかなと感じています。

それは置いておいて、本日はspaCyの機能紹介をしたいので、
既に準備されている英語のモデルを使います。

まずは、英語解析の準備をします。

import spacy
nlp = spacy.load('en')
doc1 = nlp(u"this is a pen.")

文章を単語ごとに区切る

文章を単語ごとに区切ることが可能です。

for token in doc1:
    print (token)

結果はスペースで区切られている次の通りです。

this
is
a
pen
.

文章を文ごとに分ける

簡単そうな例ですが、文章を文ごとに分解します。
英語だと、ピリオドによる分割になるでしょうか。

doc2 = nlp(u"This is a pen. That is an apple")
for sent in doc2.sents:
    print (sent)

出力結果です。「.」による分割ができています。

This is a pen.
That is an apple

固有表現抽出(Named Entity Recognizer)

固有表現抽出は文章の中から固有の表現を発見する技術です。

ent_doc = nlp("Rami Eid is studying at Stony Brook University in New York")
for ent in ent_doc.ents:
    print (ent, ent.label, ent.label_)

残念ながらNew Yorkが出力できていません。
固有表現が何かだけではなく、どんな固有表現(人なのか、組織なのか)かも抽出できています。

Rami Eid 377 PERSON
Stony Brook University 380 ORG

名詞フレーズの抽出

フレーズの抽出も可能です。

noun_chunk_test = nlp(u"Natural language processing (NLP) deals with the application of computational models to text or speech data.")
for noun_chunk in noun_chunk_test.noun_chunks:
    print (noun_chunk)

出力結果です。これもそれっぽいですね。

Natural language processing (NLP) deals
the application
computational models
text or speech data

文章間の距離を計算する。

文書のベクトルを計算します。
word2vecに類似したアルゴリズムです。実装は非常にシンプルで
apple.vectorをすれば、文書のベクトルを取得できます。
ベクトルを表示すると標準出力が長くなるので、代わりにshapeを実行してみます。

apple.vector.shape

また、2ベクトル間を計算し、類似度を算出できます。

udon = nlp(u"Udon and oranges are not similar.")
apple = nlp(u"Apples and oranges are similar. Boots and hippos aren't.")
udon.similarity(apple)

最後に

今回は自然言語処理のライブラリを紹介しました。
まだ、日本人向けには難しい状態でありますが、開発が進んでいるので、試しに使ってみてはいかがでしょうか。

TensorFlowにdefine by run(TensorFlow Eager)がやってきた

皆さんこんにちは
お元気ですか。今回の三連休は二郎食べて満足しました。

Chainerにはじまり、PyTorchなどdefine by runで
ニューラルネットワークを計算するフレームワークがあります。
このdefine by runは非常にRNN系のニューラルネットワークを書く際に重宝しています。

そして、10月末にTensorFlowからもdefine by run用の
インターフェースが試験的に提供されました。それがTensorFlow Eagerです。

※define by runが不明な方はこちらへ
s0sem0y.hatenablog.com

TensorFlow Eager

TensorFlow Eagerは次の公式の記事で紹介されています。
ただし、この機能はPreview段階です。
通常使う場面では問題ないと思いますが、念のため何か起こっても問題ない環境で利用してください。

research.googleblog.com

一言で言うなれば、TensorFlowのdefine by run用のインターフェースです。
次の特徴があるそうです。

  1. 高速なデバッグ
  2. Pythonを利用した動的なモデル
  3. TensorFlowの処理が大体実行可能

インストール

pipに含まれるTensorFlowに含まれていないため、別途インストールが必要です。
1.4.0ではimportをできますが、内部の実装がありません。
そのため、お試しで使うには次のコマンドを実行してください。

pip install tf-nightly

Example集

公式のサンプル集は次に掲載されています。とりあえず、MNISTを使います。
github.com

MNIST サンプルのご紹介

公式MNISTを紹介します。簡単に実装を理解するならばMNISTがわかりやすい。

準備

tensorflow eagerでは最初に次のコードを実行する必要があります。
このコードの実行によりtensorflow eagerでの実行ができます。所謂おまじないのようなコードです。

  tfe.enable_eager_execution()

そして、モデル、最適関数、データセットの準備をします。
MNISTModelの内部のコードに動かすニューラルネットワークを定義します。
実装方式は次で紹介します。

  # Load the datasets
  (train_ds, test_ds) = load_data(FLAGS.data_dir)
  train_ds = train_ds.shuffle(60000).batch(FLAGS.batch_size)

  # Create the model and optimizer
  model = MNISTModel(data_format)
  optimizer = tf.train.MomentumOptimizer(FLAGS.lr, FLAGS.momentum)

モデル

モデル部分のコードです。ChainerのChainやPyTorchのModuleと殆ど似ています。
__init__側に各モジュールを定義します。
Convolutionの計算や全結合層の計算モジュールを定義し、インスタンス変数として定義します。
__init__側で定義するのは、更新するパラメータを持つモジュールです。

計算の仕方はcall関数に定義します。ここで変数に対してどう計算するかを定義します。

class MNISTModel(tfe.Network):
  def __init__(self, data_format):
    super(MNISTModel, self).__init__(name='')
    if data_format == 'channels_first':
      self._input_shape = [-1, 1, 28, 28]
    else:
      assert data_format == 'channels_last'
      self._input_shape = [-1, 28, 28, 1]
    self.conv1 = self.track_layer(
        tf.layers.Conv2D(32, 5, data_format=data_format, activation=tf.nn.relu))
    self.conv2 = self.track_layer(
        tf.layers.Conv2D(64, 5, data_format=data_format, activation=tf.nn.relu))
    self.fc1 = self.track_layer(tf.layers.Dense(1024, activation=tf.nn.relu))
    self.fc2 = self.track_layer(tf.layers.Dense(10))
    self.dropout = self.track_layer(tf.layers.Dropout(0.5))
    self.max_pool2d = self.track_layer(
        tf.layers.MaxPooling2D(
            (2, 2), (2, 2), padding='SAME', data_format=data_format))

  def call(self, inputs, training):
    x = tf.reshape(inputs, self._input_shape)
    x = self.conv1(x)
    x = self.max_pool2d(x)
    x = self.conv2(x)
    x = self.max_pool2d(x)
    x = tf.layers.flatten(x)
    x = self.fc1(x)
    if training:
      x = self.dropout(x)
    x = self.fc2(x)
    return x

メインループ

サンプルのメインとなる処理です。
次のコードでは、1poch分の学習を行い、1epoch分学習が完了したモデルを保存しています。

  with tf.device(device):
    for epoch in range(1, 11):
      with tfe.restore_variables_on_create(
          tf.train.latest_checkpoint(FLAGS.checkpoint_dir)):
        global_step = tf.train.get_or_create_global_step()
        start = time.time()
        with summary_writer.as_default():
          train_one_epoch(model, optimizer, train_ds, FLAGS.log_interval)
        end = time.time()
        print('\nTrain time for epoch #%d (global step %d): %f' % (
            epoch, global_step.numpy(), end - start))
      with test_summary_writer.as_default():
        test(model, test_ds)
      all_variables = (
          model.variables
          + optimizer.variables()
          + [global_step])
      tfe.Saver(all_variables).save(
          checkpoint_prefix, global_step=global_step)

学習

次のコードは学習するコードです。
モデル 部分で定義したmodelを使います。全体の流れは次の通りです。

  1. バッチごとのデータセットiteratorで取得する。(tfe.iterator(dataset))
  2. 誤差を計算する(model loss)
  3. 最適化関数で最小化する(optimizer.minimize)

定義したモデルはmodel()で実行できます。

def train_one_epoch(model, optimizer, dataset, log_interval=None):
  """Trains model on `dataset` using `optimizer`."""

  tf.train.get_or_create_global_step()

  def model_loss(labels, images):
    prediction = model(images, training=True)
    loss_value = loss(prediction, labels)
    tf.contrib.summary.scalar('loss', loss_value)
    tf.contrib.summary.scalar('accuracy',
                              compute_accuracy(prediction, labels))
    return loss_value

  for (batch, (images, labels)) in enumerate(tfe.Iterator(dataset)):
    with tf.contrib.summary.record_summaries_every_n_global_steps(10):
      batch_model_loss = functools.partial(model_loss, labels, images)
      optimizer.minimize(
          batch_model_loss, global_step=tf.train.get_global_step())
      if log_interval and batch % log_interval == 0:
        print('Batch #%d\tLoss: %.6f' % (batch, batch_model_loss()))

判定

判定部は次のとおりです。モデルを使って出力し、誤差を計算しているなど
学習時に見られるコードのため、目立って新しい箇所はないかと思います。

def test(model, dataset):
  """Perform an evaluation of `model` on the examples from `dataset`."""
  avg_loss = tfe.metrics.Mean('loss')
  accuracy = tfe.metrics.Accuracy('accuracy')

  for (images, labels) in tfe.Iterator(dataset):
    predictions = model(images, training=False)
    avg_loss(loss(predictions, labels))
    accuracy(tf.argmax(predictions, axis=1, output_type=tf.int64),
             tf.argmax(labels, axis=1, output_type=tf.int64))
  print('Test set: Average loss: %.4f, Accuracy: %4f%%\n' %
        (avg_loss.result(), 100 * accuracy.result()))
  with tf.contrib.summary.always_record_summaries():
    tf.contrib.summary.scalar('loss', avg_loss.result())
    tf.contrib.summary.scalar('accuracy', accuracy.result())

標準出力

MNISTのサンプルを動作させると次の出力になります。

$ python mnist.py
/Users/Tereka/anaconda3/lib/python3.6/importlib/_bootstrap.py:205: RuntimeWarning: compiletime version 3.5 of module 'tensorflow.python.framework.fast_tensor_util' does not match runtime version 3.6
  return f(*args, **kwds)
2017-11-05 23:14:38.631714: I tensorflow/core/platform/cpu_feature_guard.cc:137] Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.2 AVX AVX2 FMA
Using device /cpu:0, and data format channels_last.
Extracting /tmp/tensorflow/mnist/input_data/train-images-idx3-ubyte.gz
Extracting /tmp/tensorflow/mnist/input_data/train-labels-idx1-ubyte.gz
Extracting /tmp/tensorflow/mnist/input_data/t10k-images-idx3-ubyte.gz
Extracting /tmp/tensorflow/mnist/input_data/t10k-labels-idx1-ubyte.gz
Batch #0	Loss: 2.295565
Batch #10	Loss: 2.279653
Batch #20	Loss: 2.263355
Batch #30	Loss: 2.227737
Batch #40	Loss: 2.176997
Batch #50	Loss: 2.159155
Batch #60	Loss: 1.995388
Batch #70	Loss: 1.832986
Batch #80	Loss: 1.613709
Batch #90	Loss: 1.197411

仮にTypeErrorが発生した場合は、該当する引数を消してください。私の場合、次の箇所で発生しました。

TypeError: create_summary_file_writer() got an unexpected keyword argument 'flush_secs'

最後に

MNISTの公式サンプルをご紹介しました。
RNNを書く時に非常に使いやすくなりそうです。また、フレームワーク戦争が熾烈になりそう。

ZabbixでGPUを監視する

皆さんこんにちは
お元気ですか。私はISUCONで負けました。

前回はCPUの温度監視をしました。だが、まだ情報が足りません。

そう、GPUの監視情報です。一般的にはnvidia-smiから情報を取得できますが
これをZabbixで見たいと思います。

同じようなことを考える人は古今東西にいるもので、
Zabbix Agentの設定、テンプレートまで公開されています。それに沿って可視化しましょう。

github.com

Zabbix Agentの設定

Zabbix AgentにUserParameterを設定します。
このパラメータを設定することでZabbixAgentに新しくメトリックを追加できます。

今回のテンプレートでは、次の内容を追加します。

UserParameter=gpu.temp,nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits -i 0
UserParameter=gpu.memtotal,nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits -i 0
UserParameter=gpu.used,nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits -i 0
UserParameter=gpu.free,nvidia-smi --query-gpu=memory.free --format=csv,noheader,nounits -i 0
UserParameter=gpu.fanspeed,nvidia-smi --query-gpu=fan.speed --format=csv,noheader,nounits -i 0
UserParameter=gpu.utilisation,nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits -i 0
UserParameter=gpu.power,nvidia-smi --query-gpu=power.draw --format=csv,noheader,nounits -i 0

テンプレートの設定

さて、githubにテンプレート(nvidia_smi_template.xml)があります。
このテンプレートをZabbixに読み込ませます。

f:id:tereka:20171015014349p:plain

そして、Hostsから対象のサーバにテンプレートを適用します。

f:id:tereka:20171015014451p:plain

さて、この状態でグラフを見ます。4種類のグラフが増えています。

  1. GPU Tempareture・・・GPUの温度の確認ができる
  2. GPU Power・・・GPUの消費電力の確認ができる
  3. GPU Memory・・・GPUメモリの状態の確認ができる
  4. GPU Utilisation・・・GPUの利用率を確認できる

試しにこのグラフを表示します。一応全てのキャプチャを撮影しました。

GPU Tempareture
f:id:tereka:20171015015217p:plain

GPU Power
f:id:tereka:20171015015243p:plain

GPU Memory
f:id:tereka:20171015015233p:plain

GPU Utilisation
f:id:tereka:20171015014738p:plain

それぞれ監視ができています。この状態で、異常が起こった場合の状態を確認できます。

最後に

簡単にGPUの状態を監視できました。
とりあえず、これを眺めて楽しみます。
※様々なことを行って調べたところ私のマシントラブルはマザーボードが原因だったようです。

ZabbixでサーバマシンのCPU温度を監視する

皆さんこんにちは。
お元気ですか。旧友に最近太ったといわれて少々、落ち込んでます。
ちょっと食べる量減ったんですよね。ここ数年。

負荷がかかるとリモートPCの調子が時々悪くなると言った事象が発生し、
勝手に落ちるするといった状況なのでちょっと対策を打ちたいなぁと思っていました。

CPU温度が怪しいと思っていましたが、自分の目でsensorsを監視し続けるのは大変でした。
また、非常にエンジニア的ではなく気に入らないので別の手段(自動化など)を考えていました。
調べると、Zabbixだと収集が簡単そうにできるので、勉強がてらやってみました。

Zabbix

Zabbixとは

多数のネットワークやサーバを監視するためのソフトウェアです。
CPU, メモリなどのメトリックの収集や監視、条件による通知を行うことができます。
オープンソースでも使えるそうで、これでZabbixで温度監視ができるようです。

www.zabbix.com

Zabbixのインストール

まずは、必要な媒体をインストールします。(この方法はUbuntu/Debian用です)

wget http://repo.zabbix.com/zabbix/3.2/ubuntu/pool/main/z/zabbix-release/zabbix-release_3.2-1+xenial_all.deb
sudo dpkg -i zabbix-release_3.2-1+xenial_all.deb
sudo apt-get update
apt-get install zabbix-server-pgsql zabbix-frontend-php
sudo apt-get install zabbix-agent

普段使っているデータベースがpostgresqlなので、postgresql用の設定を行います。
まずは、Zabbix用のデータベースを作成します。

psql -U postgres
create database zabbix; 

次にZabbixを使うにあたり、必要なデータベース設定を行います。

sudo zcat /usr/share/doc/zabbix-server-pgsql/create.sql.gz | sudo psql -U postgres zabbix

そして、Zabbixのデータベースの設定を行います。
/etc/zabbix/zabbix_server.confに次の内容を記載します。
内容は設定環境に応じて、変更をしてください。

DBHost=localhost
DBName=zabbix
DBUser=zabbix
DBPassword=<PASSWORD>

そして、Zabbix Serverを起動します。

service zabbix-server start
update-rc.d zabbix-server enable

実はここまででZabbix動くと思えば、どうやら、Time/Zoneの設定が必要なようです。
この項目でGUI PreferenceのチェックでNGが出ました。
/etc/zabbix/apache.confのphp部分でコメントアウトになっている設定を外して、
Time/Zoneを適切に設定します。(日本であればAsia/Tokyo)

<Directory "/usr/share/zabbix">
    Options FollowSymLinks
    AllowOverride None
    Order allow,deny
    Allow from all

    <IfModule mod_php5.c>
        php_value max_execution_time 300
        php_value memory_limit 128M
        php_value post_max_size 16M
        php_value upload_max_filesize 2M
        php_value max_input_time 300
        php_value always_populate_raw_post_data -1
        php_value date.timezone Asia/Tokyo
    </IfModule>
    <IfModule mod_php7.c>
        php_value max_execution_time 300
        php_value memory_limit 128M
        php_value post_max_size 16M
        php_value upload_max_filesize 2M
        php_value max_input_time 300
        php_value always_populate_raw_post_data -1
        php_value date.timezone Asia/Tokyo
    </IfModule>
</Directory>

最後にサーバに設定を反映させます。

sudo service apache2 restart

GUI設定

まずは、Zabbixの設定をGUIで行います。
http://<サーバのIP>/zabbix/setup.php

f:id:tereka:20171014233141p:plain

DBの設定を記述します。今回はpostgreSQLを選択し、設定を記載します。

f:id:tereka:20171014233124p:plain

これでダッシュボードにアクセスすることができます。
※ログインですがIDはAdmin, passwordはzabbixです。

f:id:tereka:20171015002345p:plain

試しにCPUを可視化するとこんな感じです。

f:id:tereka:20171014234017p:plain

CPUの温度監視

さて、ここからが本題です。CPUの温度を監視します。
手順は次のとおりです

  1. バイスの特定
  2. アイテムの登録
  3. グラフの作成

バイスの特定

sensorsコマンドを使ってデバイス名とコアを特定する必要があります。
sensors -u を実行しましょう。

$ sensors -u
acpitz-virtual-0
Adapter: Virtual device
temp1:
  temp1_input: 27.800
  temp1_crit: 105.000
temp2:
  temp2_input: 29.800
  temp2_crit: 105.000

coretemp-isa-0000
Adapter: ISA adapter
Physical id 0:
  temp1_input: 60.000
  temp1_max: 80.000
  temp1_crit: 100.000
  temp1_crit_alarm: 0.000
Core 0:
  temp2_input: 51.000
  temp2_max: 80.000
  temp2_crit: 100.000
  temp2_crit_alarm: 0.000
Core 1:
  temp3_input: 50.000
  temp3_max: 80.000
  temp3_crit: 100.000
  temp3_crit_alarm: 0.000
Core 2:
  temp4_input: 54.000
  temp4_max: 80.000
  temp4_crit: 100.000
  temp4_crit_alarm: 0.000
Core 3:
  temp5_input: 60.000
  temp5_max: 80.000
  temp5_crit: 100.000
  temp5_crit_alarm: 0.000

asus-isa-0000
Adapter: ISA adapter
cpu_fan:
  fan1_input: 0.000

この長い出力の中からデバイス名coretemp-isa-0000と
対応する温度temp2~temp5をどこかに記録します。Zabbixの設定で使います。

アイテムの登録

そしてZabbixのアイテムを登録しましょう。
Zabbixのアイテムには先程記録したデバイス名と対応する温度を使います。
温度計測対象分、この登録を繰り返します。

Configuration→Hosts→サーバ名を選択→Itemsで開けます。

f:id:tereka:20171015000640p:plain

グラフの作成

最後にグラフを作成します。
先程、登録したアイテムを使用してグラフを作成します。

f:id:tereka:20171015232812p:plain

f:id:tereka:20171015000617p:plain

そして暫く放置すると遂に、CPUの温度をグラフに表示できました。
これで常にアクセスし続けていれば、監視がスムーズにできます。

f:id:tereka:20171015001643p:plain

最後に

CPU監視をとても楽にできました。意外に使い心地が良いですね。
この情報に加えて、もう少し別のデータも監視したいので調べています。