tokuhirom's blog

circleci_ikachan を書いた

https://github.com/tokuhirom/circleci_ikachan

circle ci の webhook を受け取って、ikachan に forward するやつです。 ちょっと必要だったので書きました。

今回は、go で書いてみました。

Created: 2016-06-24 23:33:06 +0000
Updated: 2016-06-24 23:33:06 +0000

Promgen talk - Prometheus casual talks

I promoted promgen, a prometheus configuration management console web app at Prometheus Casual Talks.

I'll be release it as OSS application.

Created: 2016-06-17 03:13:52 +0000
Updated: 2016-06-17 03:13:52 +0000

How do I display custom element with IncrementalDOM?

Incremental DOM is great library to build dynamic DOM tree.

If you want to build DOM tree contains custom tags, you need to call IncrementalDOM.skip() to skip patching DOM elements inside custom element.

<!doctype html>
<html>
  <head>
    <script type="text/javascript" src="node_modules/incremental-dom/dist/incremental-dom.js"></script>
  </head>
<body>

<div id="container"></div>

<script>
document.registerElement('x-bar', (function () {
  var proto = Object.create(HTMLElement.prototype);
  proto.createdCallback = function () {
    IncrementalDOM.patch(this, function () {
      IncrementalDOM.elementOpen('div');
      IncrementalDOM.text('hello');
      IncrementalDOM.elementClose('div');
    });
  };
  return {prototype:proto};
})());

const container = document.getElementById('container');
for (let i=0; i<3; ++i) {
  IncrementalDOM.patch(container, function () {
    IncrementalDOM.elementOpen('x-bar');
    IncrementalDOM.skip();
    IncrementalDOM.elementClose('x-bar');
  });
}

</script>

</body>
</html>
Created: 2016-06-14 06:25:16 +0000
Updated: 2016-06-14 06:25:16 +0000

x-tag で delegate しているときに root element を取得する方法

http://stackoverflow.com/questions/30895067/x-tag-event-delegation-accessing-the-root-element

xtag.register('x-foo', {
  content: '<input /><span></span>',
  events: {
    focus: function(e){
      // e.currentTarget === your x-foo element
    },
    'tap:delegate(span)': function(e){
      // e.currentTarget still === your x-foo element
      // 'this' reference set to matched span element
    }
  }
});

のように e.currentTarget を見れば取得可能。

Created: 2016-06-06 23:28:25 +0000
Updated: 2016-06-06 23:28:25 +0000

spring-data-elasticsearch について

ES 2 が出てから半年以上経過しているにもかかわらず 2 対応がされていない。 https://www.elastic.co/blog/elasticsearch-2-0-0-released https://github.com/spring-projects/spring-boot/issues/4339

クエリをすべてラップしているために 2 対応がなかなか難しいのだと思う。

Elasticsearch の場合、1系用のクライアントライブラリで2系にアクセスすると接続拒否されるし、なかなか厳しい。。

_score の取得が一筋縄ではいかない。 http://stackoverflow.com/questions/35175319/spring-data-elasticsearch-metadata-score

もともと elasticsearch 自体の java client は良く出来ているので、あまりラッパをかます必要がないのになぜかましてしまったのか。。

「楽になっている部分 < 負担になっている部分」という感じで、採用メリットが薄いし、後々負債になる可能性が高いと思う。 (この手の「便利なラッパークラス」にありがちだが)

Created: 2016-06-03 22:52:45 +0000
Updated: 2016-06-03 22:52:45 +0000

[prometheus][java] Added Spring Boot Metrics integration to Prometheus' simpleclient_java

https://github.com/prometheus/client_java/pull/114

I sent p-r for clientjava repository ... I want to export spring boot metrics to simpleclientservlet. The p-r was merged in master branch. The patch will include in next release.

Created: 2016-05-26 05:02:30 +0000
Updated: 2016-05-26 05:02:30 +0000

[golang] json_path_scanner 書いた

https://github.com/tokuhirom/json_path_scanner

JSON を読み取ったデータ構造を食わせると、「JSON Path」と「値」のペアのリストを得られるというやつ。 すでにありそうだったけど見当たらなかったので書いた。

Created: 2016-05-22 00:19:58 +0000
Updated: 2016-05-22 00:19:58 +0000

[prometheus] apache_exporter なおした

https://github.com/neezgee/apache_exporter Prometheus で apache を監視するための agent として apache_exporter があるが、これが全く動いてなかったので、動くようになおしておきました。

Created: 2016-05-18 00:18:37 +0000
Updated: 2016-05-18 00:18:37 +0000

[ruby] 10s 10m みたいな文字列から秒数を求めるには chronic_duration を使う

https://github.com/hpoydar/chronic_duration

なにかの実行間隔のような設定がある場合、chronicduration を利用してパースすることができる。 実行例は以下の通り。integer をそのまま渡すとエラーになるので、integer が渡ってきそうなケースでは tos して渡すようにするのがよさそう。 パースできない場合には nil が返ってくるので、nil もケアしてあげる必要があります。

[1] pry(main)> require 'chronic_duration'
=> true
[2] pry(main)> ChronicDuration.parse(3)
NoMethodError: undefined method `downcase' for 3:Fixnum
from /Users/tokuhirom/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/chronic_duration-0.10.6/lib/chronic_duration.rb:181:in `cleanup'
[3] pry(main)> ChronicDuration.parse(3.to_s)                                                                                                  
=> 3
[4] pry(main)> ChronicDuration.parse('3')                                                                                                      
=> 3
[5] pry(main)> ChronicDuration.parse('3s')
=> 3
[6] pry(main)> ChronicDuration.parse('3m')                                                                                                     
=> 180
[7] pry(main)> ChronicDuration.parse('3h')                                                                                                     
=> 10800
[8] pry(main)> 3*60*60
=> 10800
[9] pry(main)> ChronicDuration.parse("carrot")
=> nil
Created: 2016-05-17 01:21:38 +0000
Updated: 2016-05-17 01:21:38 +0000

sequel の migration に関するメモ

Sequel には migration 機能がついている。これを利用するには以下のようなファイルを作成する。

Sequel.migration do
  change do
    create_table(:artists) do
      primary_key :id
      String :name, :null=>false
    end
  end
end

ファイル名は db/migrations/001_init.rb とかにする。

rakefile に以下のように書く。DATABASE_URL=sqlite://hoge rake db:migrate とかすればマイグレーションされる。 namespace :db do desc "Run migrations" task :migrate, [:version] do |t, args| require "sequel" Sequel.extension :migration db = Sequel.connect(ENV.fetch("DATABASE_URL")) if args[:version] puts "Migrating to version #{args[:version]}" Sequel::Migrator.run(db, "db/migrations", target: args[:version].to_i) else puts "Migrating to latest" Sequel::Migrator.run(db, "db/migrations") end end end

と、公式のマニュアルに書いてあった。 https://github.com/jeremyevans/sequel/blob/master/doc/migration.rdoc

Created: 2016-05-15 09:32:54 +0000
Updated: 2016-05-15 09:32:54 +0000

個人的な grafana に対する不満トップ3

Editable をオフにすると戻せない

https://github.com/grafana/grafana/issues/2554

You can make it editable again using the Save As... feature and enter the same name. I agree though, there should be an easier way to make it editable.

えぇ~ そんなことってある? ッて感じのISSUE。

This is done and available in nightly and soon grafana 3.0 とのことなので 3.0 を正座して待つしかない

ダッシュボードの設定変更履歴が把握できないし、誰かがいじっても戻せない

https://github.com/grafana/grafana/issues/4638 誰かがいじっちゃったとしてそれを把握したり復元したりできないと辛い。。

4.0 まででなさそうで辛い。

Home Dashboard のロゴ変えられない

変えたい。

Created: 2016-05-10 10:21:23 +0000
Updated: 2016-05-10 10:21:23 +0000

CPU の system が妙に高いぞって時に犯人さがすには pidstat 使えば良い。

pidstat -h -u | sort -nr -k 5,5 | head すれば良い。

sudo yum install -y sysstat してインストール。

簡単に誰が system 消費してるかわかる [tokuhirom@centos-1gb-sgp1-01 ~]$ pidstat -h -u | sort -nr -k 5,5 | head 1462874412 0 27 0.00 0.22 0.00 0.22 0 kswapd0 1462874412 0 19281 0.16 0.09 0.00 0.25 0 cadvisor 1462874412 0 342 0.01 0.02 0.00 0.03 0 systemd-journal 1462874412 0 377 0.00 0.01 0.00 0.01 0 auditd 1462874412 0 262 0.00 0.01 0.00 0.01 0 jbd2/vda1-8 1462874412 0 16958 0.02 0.01 0.00 0.02 0 tuned 1462874412 0 13775 0.00 0.01 0.00 0.01 0 xfsaild/dm-9 1462874412 0 11 0.00 0.01 0.00 0.01 0 rcuos/0 1462874412 0 10 0.00 0.01 0.00 0.01 0 rcu_sched

Created: 2016-05-10 10:01:11 +0000
Updated: 2016-05-10 10:01:11 +0000

blog のバグを直した

ブログシステムをうっかり書き直したらちょっとバグってたので直した。

  • ctime/mtime を timestamp で保持していたが git なので保持されてなくて死んでたのでファイルの中に ctime/mtime を入れるようにした
  • entry の個別ページの title タグがちゃんと入ってなかったので入れた
Created: 2016-05-09 14:40:25 +0000
Updated: 2016-05-09 14:40:25 +0000

[java][linux] Java の各プロセスのスレッド数を得るワンライナー

jps -l | ruby -ne 'pid, cmd = $_.chomp.split(/ /); puts pid + "\t" + File.read("/proc/#{pid}/stat").split(/ /)[19] + "\t" + cmd'

linux の procfs から取得していくスタイル

Created: 2016-05-09 10:08:27 +0000
Updated: 2016-05-09 10:08:27 +0000

javaagent として動く java のサンプラを書いた

java アプリケーションのサンプリングを簡単にやるための簡単なライブラリを書いた。javaagent の習作的なやつです。

https://github.com/tokuhirom/java-samples/tree/master/nanojsampler

たんに、定期的に Thread.getAllStackTraces() して、よしなに集計した結果を jmx に流し込んでいる。

package me.geso.nanojsampler;

import javax.management.*;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class Nanojsampler {
    private final Map<StackTraceElement, Integer> map = new ConcurrentHashMap<>();

    public static void premain(String agentArgs, Instrumentation inst) throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException {
        int sleepMillisec = Integer.parseInt(agentArgs);
        Nanojsampler nanojsampler = new Nanojsampler();
        nanojsampler.initJmx();

        Thread thread = new Thread(() -> {
            try {
                while (true) {
                    nanojsampler.poll();
                    Thread.sleep(sleepMillisec);
                }
            } catch (InterruptedException ignored) {
                Thread.currentThread().interrupt();
            }
        });
        thread.setName("nanojsampler");
        thread.start();

        System.out.println("done");
    }

    public void initJmx() throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException {
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        ObjectName objectName = new ObjectName("nanojsampler:type=report");
        NanojsamplerReport nanojsamplerMBean = new NanojsamplerReport(map);
        mbs.registerMBean(nanojsamplerMBean, objectName);
    }

    public void poll() {
        Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
        for (Map.Entry<Thread, StackTraceElement[]> threadEntry : allStackTraces.entrySet()) {
            process(threadEntry);
        }
    }

    private void process(Map.Entry<Thread, StackTraceElement[]> threadEntry) {
        Thread thread = threadEntry.getKey();
        StackTraceElement[] traceElements = threadEntry.getValue();

        // ignore waiting threads
        if (thread.getState() == Thread.State.TIMED_WAITING || thread.getState() == Thread.State.WAITING) {
            return;
        }

        for (StackTraceElement traceElement : traceElements) {
            map.compute(traceElement,
                    (key2, oldValue) -> oldValue == null ? 1 : oldValue + 1);
        }
    }

    public interface NanojsamplerReportMBean {
        List<String> getReport();
    }

    public static class NanojsamplerReport implements NanojsamplerReportMBean {
        private final Map<StackTraceElement, Integer> map;

        public NanojsamplerReport(Map<StackTraceElement, Integer> map) {
            this.map = map;
        }

        @Override
        public List<String> getReport() {
            return this.map.entrySet().stream()
                    .sorted(Comparator.comparing(Map.Entry::getValue, Comparator.reverseOrder()))
                    .map(NanojsamplerReport::convertEntry)
                    .collect(Collectors.toList());
        }

        private static String convertEntry(Map.Entry<StackTraceElement, Integer> stackTraceElementIntegerEntry) {
            StackTraceElement element = stackTraceElementIntegerEntry.getKey();
            Integer value = stackTraceElementIntegerEntry.getValue();

            return value + "\t" + element.getClassName() + "." + element.getMethodName() + ":" + element.getFileName() + ":" + element.getLineNumber();
        }
    }
}

build.gradle に以下のように記載して、Premain-class を定義する。src/main/resources/META-INF/MANIFEST.MF に直接ファイル置いても上書きされるので注意。 jar { manifest { attributes( "Agent-Class": "me.geso.nanojsampler.Nanojsampler", "Premain-Class": "me.geso.nanojsampler.Nanojsampler") } }

集計処理をインプロセスでやっているので重くなりがちかもしれない。が、現実的にはそれほど重くはならないか。 いずれにせよサンプルコードなのでまあ良し。

Created: 2016-05-09 00:18:59 +0000
Updated: 2016-05-09 00:18:59 +0000

[docker] alpine linux を使うなら gosu じゃなくて su-exec で良い

gosu という setuidgid みたいなツールがあるが、よく README を読むと

When using Alpine, it's probably also worth checking out su-exec (apk add --no-cache su-exec), which since version 0.2 is fully gosu-compatible in a fraction of the file size.

と記載されており、su-exec を使えば良いとのこと(su-exec のほうが C なので読みやすいし)。

Created: 2016-05-08 21:10:30 +0000
Updated: 2016-05-08 21:10:30 +0000

Java mixed-mode flame graphsについてのメモ

http://www.brendangregg.com/blog/2015-11-06/java-mixed-mode-flame-graphs.html

Java mixed-mode flame graphs ってエントリがどんな問題を解決しているか

  • perf を用いた軽量なプロファイリングを java でも
    • perf-map-agent を利用することで java のメソッドもプロファイリング可能
    • Java のプロファイリングと kernel/libc などの system profiling をいっぺんにできる
    • この2つのものを同時にプロファイリングできることを mixed-mode といっているようだ
  • flame graphs で見やすいグラフを
    • NYTProf とかで出てる奴ね

といった問題を解決しているのだが、解決している問題が多岐に渡っていて理解しにくい。

必要な環境

  • jdk8u60+
  • linux
  • perf

とりあえず試す

sudo yum install -y perf

で perf を install

sudo yum install -y java-1.8.0-openjdk-devel

openjdk なら -devel パッケージもインストールしないと perf-map-agent をビルドできない(oraclejdk でどうなのかは未検証)。

perf のメトリクス取るのの基本

sudo perf record -F 99 -ag -- sleep 30 で、99Hz で 30 sec のプロファイリング。

sudo perf report -f でレポートを curses で見れる

perf-map-agent を使う

https://github.com/jrudolph/perf-map-agent

[tokuhirom@develop perf-map-agent]$ git clone https://github.com/jrudolph/perf-map-agent.git                                                                                                        
Cloning into 'perf-map-agent'...
remote: Counting objects: 336, done.
remote: Total 336 (delta 0), reused 0 (delta 0), pack-reused 336
Receiving objects: 100% (336/336), 95.16 KiB | 0 bytes/s, done.
Resolving deltas: 100% (165/165), done.
[tokuhirom@develop perf-map-agent]$ cmake .
-- Found JNI: /usr/lib/jvm/jre/lib/amd64/libjawt.so  
-- JNI_INCLUDE_DIRS=/usr/lib/jvm/java/include;/usr/lib/jvm/java/include/linux;/usr/lib/jvm/java/include
-- JNI_LIBRARIES=/usr/lib/jvm/jre/lib/amd64/libjawt.so;/usr/lib/jvm/jre/lib/amd64/server/libjvm.so
-- JAVA_INCLUDE_PATH=/usr/lib/jvm/java/include
-- JAVA_INCLUDE_PATH2=/usr/lib/jvm/java/include/linux
-- Found Java: /bin/java (found version "1.8.0.91") 
-- Configuring done
-- Generating done
-- Build files have been written to: /home/tokuhirom/perf-map-agent

perf-map-agent というコマンドラインツールを用意する。これが特定の PID の java process に attach して、JVM TI (JVM Tool Inteface) で情報を集めて perf 用の形式で出力してくれる。このツールを利用するには -XX:+PreserveFramePointer をターゲットの jvm の起動時につける必要がある(このオプションは java 8u60+ で利用可能)。

create-java-perf-map.sh

./bin/perf-java-top

localdev-provisioning%20%E2%80%94%20tokuhirom@develop:~%20%E2%80%94%20ssh%20%E2%97%82%20vagrant%20ssh%20and%20blog%20admin

net.virtualvoid.perf.AttachOnce 実行して map を取得したあと、perf top している。↑↑のスクショを見ると、java method の情報も取得できていることがわかる。

まとめ

  • perf-map-agent 便利
    • 得に、Atlassian など導入しているときにボトルネックさっくり知りたいとかそういうケースでは恩恵がおおきそう
    • あるいは norikra とか
  • 通常の web app の場合、適用範囲は限定的かもしれない
    • しかし、perf ほど気軽に使えるものもあんまないのでこの方法は有りかもしれない
  • perf-map-agent はjvmti を利用するCコードをターゲットVMにねじ込む
    • バグがあるとこれ起因でクラッシュするかもしれない
Created: 2016-05-08 14:37:03 +0000
Updated: 2016-05-09 03:38:10 +0000

ansistrano について

https://github.com/ansistrano/deploy/

Ansible 上で capistrano 的にデプロイできるようにするというプロジェクトがある。 これは一見良さそうだが、設定を真面目にやっていくと、同等のことを自分で手で書くのと同程度に大量の設定を書くことになるのが気になる。

Created: 2016-05-02 05:24:54 +0000
Updated: 2016-05-02 05:24:54 +0000

Alfred のワークフローを Perl で書きたい!

https://github.com/tokuhirom/p5-Alfred

なんか Alfred2 を久々に使い始めている。

最近は IDE で生活する時間が長いので、terminal を開いてそこから peco を開いてみたいなことをするよりは、Alfred2 からサーバー選んで、そこから open してターミナル開いたほうがいいなぁと思ってみたという感じです。

で、perl で workflow を書いたらいいんだけど、perl 用のライブラリがなかったので書いた。

要するに、定義にしたがって、XML を吐くスクリプトがありゃいいんで、吐くようにしたってだけ。

ご利用ください。

これを使って、社内のデプロイツールからサーバーリスト撮ってきて、ターミナル開く機能と、サーバーリストからサーバーグループをいっきに csshX で開くってなツールを書きました。

あと、kurado を開くやつを作ろうと思ったけど、kurado には service list を取得する API がないのであきらめた。

Created: 2016-05-02 05:24:54 +0000
Updated: 2016-05-05 04:28:37 +0000

rxjava で peek() みたいなことしたい

java8 stream api でいうところの peek をするには doOnNext を利用すればよい。

http://stackoverflow.com/questions/30760768/rxjava-performing-a-peek-or-void-operation-within-an-observable-chain

Created: 2016-05-02 05:24:54 +0000
Updated: 2016-05-02 05:24:54 +0000

spring-boot のメトリクスデータを graphite に送る

graphite とはまあザックリ言うと growthforecast みたいなものだが、python で書かれていて、ダッシュボード的な機能が充実してるって感じのものです。

画面の様子は以下の様な感じ。

Graph Tree:

Dashboard:

以下の様な感じで、nc で直接書き込める。http based な gf とはちょっと違う。

PORT=2003
SERVER=graphite.your.org
echo "local.random.diceroll 4 `date +%s`" | nc -q0 ${SERVER} ${PORT}

で、こういうやつにメトリクスデータを送るにはどうしたらいいか。spring-boot 自体はメトリクスの出力先として statsd, redis, opentsdb しかサポートしていない。 statsd から graphite に書き込ませることもできるのだが、今回は直接 graphite に書かせてみた。statsd 経由でやる場合は、各ホストに statsd を動かすってことになるのかなあ。 あんまり statsd 使ってる話を聞かないのでよくわからない。そして、graphite に送るだけなら、直接 graphite に送ればいい気もする。


さて、spring-boot で graphite を送るには、例によって dropwizard-metrics を利用すれば良い。

依存に以下を足す。

compile('io.dropwizard.metrics:metrics-core:3.1.0')
compile('io.dropwizard.metrics:metrics-graphite:3.1.2')

で、実際に動くコードは以下。依存にいれておけば、org.springframework.boot.actuate.autoconfigure.MetricsDropwizardAutoConfiguration が動いて、 com.codahale.metrics.MetricRegistry を Autowired できるようになるんで、これを利用すれば良い。

package com.example.config;

import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.graphite.Graphite;
import com.codahale.metrics.graphite.GraphiteReporter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.MetricsDropwizardAutoConfiguration;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;

@Configuration
@Slf4j
public class GraphiteConfig {
    /**
     * {@link MetricsDropwizardAutoConfiguration} configures Dropwizard's MetricRegistry
     * if it's available in classpath.
     */
    @Autowired
    private MetricRegistry registry;

    @PostConstruct
    public void initialize() {
        final Graphite graphite = new Graphite(new InetSocketAddress("localhost", 2003));
        final GraphiteReporter reporter = GraphiteReporter.forRegistry(registry)
                .prefixedWith("web1.example.com")
                .convertRatesTo(TimeUnit.SECONDS)
                .convertDurationsTo(TimeUnit.MILLISECONDS)
                .filter(MetricFilter.ALL)
                .build(graphite);
        reporter.start(1, TimeUnit.MINUTES);

    }
}

簡単ですね。 full sample code is here: https://github.com/tokuhirom/java-samples/tree/master/spring-boot-graphite-demo

Created: 2016-05-02 05:24:54 +0000
Updated: 2016-05-02 05:24:54 +0000
Next page