明日にはでっかい太陽が昇るかもしれません。

「覚悟」とは!! 暗闇の荒野に!!進むべき道を切り開く事だッ!

Chat Ops はじめました?

github.com

昨年末から、担当するプロジェクトが変わり、新たにチームを編成することになったので、今度の開発環境では、ぜひともメールをチャットに置き換えたいと思い、 Let's Chat を社内の開発サーバに導入してみています。

別に Chat Ops にするつもりはなかったですが、 Trac から通知するプラグインを作成しているので、全面的に導入を狙ってみます。

導入手手順はおいおいまとめるとして、先行導入で分かった問題として、 Let's Chat はデスクトップ通知が ON/OFF しか選べないため、人によっては、 ON にした場合に通知で気が散るというものがあります。

リアルタイムにフォローをするため、私はすべてのやり取りの通知がほしいですが、メンバによっては、メンションありの場合だけ通知できるようになると便利です。

というわけで、 0.4.5 をベースにデスクトップ通知をメンションありの場合だけできるように設定を拡張してみました。

diff --git a/app/core/account.js b/app/core/account.js
index 539eb4e..baa100e 100644
--- a/app/core/account.js
+++ b/app/core/account.js
@@ -39,6 +39,9 @@ AccountManager.prototype.update = function(id, options, cb) {
         if (options.email) {
             user.email = options.email;
         }
+        if (options.hasOwnProperty('notificationsMentionedOnly')) {
+            user.notificationsMentionedOnly = options.notificationsMentionedOnly;
+        }
 
         if (options.username && options.username !== user.username) {
             var xmppConns = this.core.presence.system.connections.query({
diff --git a/app/models/user.js b/app/models/user.js
index 6eceda5..2d3b335 100644
--- a/app/models/user.js
+++ b/app/models/user.js
@@ -86,6 +86,10 @@ var UserSchema = new mongoose.Schema({
         type: String,
         trim: true
     },
+    notificationsMentionedOnly: {
+        type: Boolean,
+        default: false
+    },
     rooms: [{
        type: ObjectId,
        ref: 'Room'
@@ -271,7 +275,8 @@ UserSchema.method('toJSON', function() {
         lastName: this.lastName,
         username: this.username,
         displayName: this.displayName,
-        avatar: this.avatar
+        avatar: this.avatar,
+        notificationsMentionedOnly: this.notificationsMentionedOnly
     };
 });
 
diff --git a/media/js/views/modals.js b/media/js/views/modals.js
index 6ac2c12..48c02df 100644
--- a/media/js/views/modals.js
+++ b/media/js/views/modals.js
@@ -167,7 +167,8 @@
 
     window.LCB.NotificationsModalView = Backbone.View.extend({
         events: {
-            'click [name=desktop-notifications]': 'toggleDesktopNotifications'
+            'click [name=desktop-notifications]': 'toggleDesktopNotifications',
+            'click [name=notifications-mentionedonly]': 'toggleNotificationsMentionedOnly'
         },
         initialize: function() {
             this.render();
@@ -178,17 +179,31 @@
               .siblings().hide();
             if (!notify.isSupported) {
                 $input.attr('disabled', true);
-                // Welp we're done here
-                return;
             }
-            if (notify.permissionLevel() === notify.PERMISSION_GRANTED) {
+            else if (notify.permissionLevel() === notify.PERMISSION_GRANTED) {
                 $input.find('.enabled').show()
                   .siblings().hide();
             }
-            if (notify.permissionLevel() === notify.PERMISSION_DENIED) {
+            else if (notify.permissionLevel() === notify.PERMISSION_DENIED) {
                 $input.find('.blocked').show()
                   .siblings().hide();
             }
+
+            var $input = this.$('[name=notifications-mentionedonly]');
+            $input.find('.disabled').show()
+              .siblings().hide();
+            $.get('./notifications', function(data) {
+                if (data.status == 'error') {
+                    $input.find('.disabled').show()
+                      .siblings().hide();
+                    return;
+                }
+
+                if (data.mentionedOnly) {
+                    $input.find('.enabled').show()
+                      .siblings().hide();
+                }
+            });
         },
         toggleDesktopNotifications: function() {
             var that = this;
@@ -198,6 +213,18 @@
             notify.requestPermission(function() {
                 that.render();
             });
+        },
+        toggleNotificationsMentionedOnly: function() {
+            var that = this;
+            if (!notify.isSupported) {
+                return;
+            }
+            $.post('./notifications/mentionedOnly', function(data) {
+                if (data.status == 'error') {
+                    return;
+                }
+                that.render();
+            });
         }
     });
 
diff --git a/media/js/views/window.js b/media/js/views/window.js
index f7cda80..1809f40 100644
--- a/media/js/views/window.js
+++ b/media/js/views/window.js
@@ -168,10 +168,15 @@
             notify.config({
                 pageVisibility: false
             });
+            var that = this;
             this.client = options.client;
             this.rooms = options.rooms;
             $(window).on('focus blur unload', _.bind(this.onFocusBlur, this));
             this.rooms.on('messages:new', this.onNewMessage, this);
+
+            $.get('./notifications', function(data) {
+                that.notificationsMentionedOnly = data.mentionedOnly;
+            });
         },
         onFocusBlur: function(e) {
             this.focus = (e.type === 'focus');
@@ -193,6 +198,10 @@
                 notify.permissionLevel() != notify.PERMISSION_GRANTED) {
                 return;
             }
+            if ( !message.mentioned &&
+                this.notificationsMentionedOnly) {
+                return;
+            }
 
             var roomID = message.room.id,
                 avatar = message.owner.avatar,
diff --git a/templates/includes/modals/notifications.html b/templates/includes/modals/notifications.html
index d9e5d81..d992943 100644
--- a/templates/includes/modals/notifications.html
+++ b/templates/includes/modals/notifications.html
@@ -20,6 +20,17 @@
                             <br /><small><$ __('Please check your browser settings') $></small>
                         </span>
                     </button>
+                    <button name="notifications-mentionedonly" class="btn btn-info" style="display: block; margin-top: 10px;">
+                        <span class="enabled">
+                            Notify mentioned messages only
+                        </span>
+                        <span class="disabled">
+                            Notify any messages
+                        </span>
+                        <span class="blocked">
+                            Select notifications are <strong>blocked</strong>
+                        </span>
+                    </button>
                 </div>
             </div>
          </div>

全体の設定は以下のようにしました。

--- a/settings.yml
+++ b/settings.yml
 rooms:
   private: true
 
+ notifications:
+   mentionedOnly: true

この修正で、とりあえずユーザ毎にメンションなしのメッセージを通知するか否かを設定できるようになりました。

あと、ついでに履歴検索で正規表現を使えるように修正しました。( 人生楽しまなきゃもったいない!: Let's Chatを入れてみた からもろパクリですが)

diff --git a/app/core/messages.js b/app/core/messages.js
index 24bb51f..5471257 100644
--- a/app/core/messages.js
+++ b/app/core/messages.js
@@ -90,7 +90,8 @@ MessageManager.prototype.list = function(options, cb) {
     }
 
     if (options.query) {
-        find = find.find({$text: {$search: options.query}});
+        var r = new RegExp(options.query, "i")
+        find = find.find({"text": {$regex:r}});
     }
 
     if (options.expand) {

デスクトップ通知については、本家の Slack が充実しているらしいので、修正を続けて、機能が充実したら master への PR も出してみたいな。

cherrypy に trac を載せた話

どういうこと?

仕事では受託開発をしており、プロジェクトにより開発環境が変わるのでポータブルなツールを利用することを心がけています。

最近は作業の管理に Issue Tracking System を利用しているのだが、(客先でのセキュリティポリシーなどの話もあり)外部のサービスは利用できません。

世間的には ITS としては Redmine が有力だと思うのですが、別途 DB が必要だったり機能が多すぎたりするので、最近勢いはないですが Python だけあれば利用できる Trac を使っています。

私が Ruby(on Rails) より Python が好きというのも大きいですが

いつもは、組み込みの Web サーバである tracd を利用しているのですが、 WSGI サーバである CherryPy を利用する場合についてまとめておきます。

プロジェクトをデプロイする

> trac-admin path\to\trac_project deploy path\to\deploy

上記のコマンドを実行すると、 path\to\deploy\cgi-bin\trac.wsgi が生成されます。

trac.wsgiCherryPy から利用するために、 wsgi.py にリネームしておきます。

CherryPy をインストールする

> pip install CherryPy

だけで OK です。

サーバスクリプトを作成する

以下のようにサーバスクリプトを作成しました。

  • path\to\deploy\cgi-bin\server.py
# Import your application as:
# from wsgi import application
# Example:

from wsgi import application

# Import CherryPy
import cherrypy

if __name__ == '__main__':

    # Mount the application
    cherrypy.tree.graft(application, "/")

    # Unsubscribe the default server
    cherrypy.server.unsubscribe()

    # Instantiate a new server object
    server = cherrypy._cpserver.Server()

    # Configure the server object
    server.socket_host = "0.0.0.0"
    server.socket_port = 80
    server.thread_pool = 30

    # Subscribe this server
    server.subscribe()

    # Start the server engine (Option 1 *and* 2)
    cherrypy.engine.start()
    cherrypy.engine.block()

完全に公式のサンプルそのままです(笑)

CherryPy を利用したのは、更に Windows サービス化を行うための前フリだったりします。

Windows サービス化については後日まとめます。

binutils がクロスコンパイルできない→とりあえずバックトレースにコードの行数を出せるようになりました

先日組んだバックトレースのコードを拡張して、ソースコードの行数を表示するために libbfd を組み込もうとした話。

#!/bin/bash

export PATH=/path/to/buildroot/output/host/usr/bin:$PATH

./configure \
        --build=$MACHTYPE \
        --host=arm-buildroot-linux-uclibcgnueabi \
        --target=arm-buildroot-linux-uclibcgnueabi \
        --with-build-sysroot=/path/to/buildroot/output/target/lib \

make

上のようなスクリプトmake すると、 gasmbstowcs がない!というエラーになる。

binutils-2.25/gas/read.c:1642: undefined reference to `mbstowcs'

とはいえ、ほしいのは libbfd.alibiberty.alibintl.a だったので今回は解決を見送った。(必要なオブジェクトはコンパイル済みだったので)

最終的には、以下のようなコードでバックトレースが出力できるようになりました。

# makefile for backtrace.

TARGET := backtrace

ARCH := arm
ifeq ($(ARCH),arm)
CROSS_COMPILE := /path/to/buildroot/output/host/usr/bin/arm-buildroot-linux-uclibcgnueabi-
else
CROSS_COMPILE :=
endif
CC := $(CROSS_COMPILE)gcc
CFLAGS := -g -O0 -I.
LDFLAGS := -rdynamic -ldl
ifeq ($(ARCH),arm)
CFLAGS += -DHAVE_CONFIG
CFLAGS += -I/path/to/binutils-2.25/bfd
CFLAGS += -I/path/to/binutils-2.25/include
LDFLAGS += -mapcs-frame
LDFLAGS += /path/to/binutils-2.25/bfd/libbfd.a
LDFLAGS += /path/to/binutils-2.25/libiberty/libiberty.a
LDFLAGS += /path/to/binutils-2.25/intl/libintl.a
else
LDFLAGS += -fno-omit-frame-pointer -lbfd
endif

all:
    $(CC) -std=c99 $(CFLAGS) -o $(TARGET) $(TARGET).c log.c $(LDFLAGS)

clean:
    rm -rf $(TARGET)
/**  @file       backtrace.c
 *  @brief      porting backtrace.
 *  @sa         http://stackoverflow.com/questions/2536021/any-porting-available-of-stacktrace-for-uclibc/2536136#2536136
 *  @sa         http://0xcc.net/blog/archives/000073.html
 */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* for Dl_info */
#endif

#if defined(HAVE_CONFIG)
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include <inttypes.h>
#include <limits.h>
#include <string.h>
#include <dlfcn.h>
#include <errno.h>
#include <bfd.h>

#include "log.h"


extern void *__libc_stack_end;


/**
 *  Frame structure.
 */
#if defined(__x86_64)
struct backtrace_frame {
    void *fp;
    void *lr;
};
#elif defined(__ARM_ARCH_ISA_ARM)
struct backtrace_frame {
    void *fp;
    void *sp;
    void *lr;
    void *pc;
};
#endif


static bfd *abfd;
static asymbol **symbols;
static int nsymbols;


#define lengthof(array) (sizeof(array)/sizeof((array)[0]))


__attribute__((constructor))
void init_bfd_stuff(void)
{
    int size;

    abfd = bfd_openr("/proc/self/exe", NULL);
    if (abfd == NULL) {
        perror("bfd_openr");
        exit(1);
    }

    bfd_check_format(abfd, bfd_object);

    size = bfd_get_symtab_upper_bound(abfd);
    if (size <= 0) {
        perror("fbd_get_symtab_upper_bound");
        exit(2);
    }

    symbols = malloc(size);
    if (symbols == NULL) {
        perror("malloc");
        exit(3);
    }
    nsymbols = bfd_canonicalize_symtab(abfd, symbols);
}

size_t Backtrace(void **frames, size_t size)
{
    void *top_frame_p;
    void *current_frame_p;
    struct backtrace_frame *frame_p;
    size_t frame_count;

    top_frame_p     = __builtin_frame_address(0);
    current_frame_p = top_frame_p;
    frame_count     = 0;

    if ((current_frame_p != NULL)
        && (current_frame_p > (void *) &frame_count)
        && (current_frame_p < __libc_stack_end)) {
    
        while ((frame_count < size)
               && (current_frame_p != NULL)
               && (current_frame_p > (void *) &frame_count)
               && (current_frame_p < __libc_stack_end)) {
        
#if defined(__x86_64)
            frame_p = (struct backtrace_frame *) (void **) current_frame_p;
#elif defined(__ARM_ARCH_ISA_ARM)
            frame_p = (struct backtrace_frame *) ((void **) current_frame_p - 3);
#endif
            frames[frame_count] = frame_p->lr;
            ++frame_count;
            current_frame_p = frame_p->fp;
        }
    }

    return frame_count;
}

void PrintBacktrace(void)
{
    void *stack[128] = { NULL }, *address;
    Dl_info info;
    asection *section = bfd_get_section_by_name(abfd, ".debug_info");
    const char *file_name;
    const char *func_name;
    int lineno;
    int found;
    int i;

    if (Backtrace(stack, lengthof(stack)) == 0) {
        fprintf(stderr, "not found backtrace...\n");
        return;
    }
    for (i = 0; stack[i] != NULL; ++i) {
        address = stack[i];

        if (section != NULL) {
            found = bfd_find_nearest_line(abfd, section, symbols, (long) address - 1, &file_name, &func_name, &lineno);
        }
        if (dladdr(address, &info) == 0) {
            fprintf(stderr, "  unknown(?+?)[%p]\n",
                    address);
        } else if (found == 0 || file_name == NULL) {
            fprintf(stderr, "  %s(%s+%p)[%p]\n",
                    info.dli_fname, info.dli_sname, address - info.dli_saddr, address);
        } else {
            fprintf(stderr, "  %s(%s+%p)[%p] at %s:%d\n",
                    info.dli_fname, info.dli_sname, address - info.dli_saddr, address, file_name, lineno);
        }
    }

    return;
}

int bar(void)
{
    printf("%s(%d) %s: called\n", __FILE__, __LINE__, __func__);
    PrintBacktrace();
    return 0;
}

int foo(void)
{
    printf("%s(%d) %s: called\n", __FILE__, __LINE__, __func__);
    bar();
    return 0;
}

int main(int argc, char **argv)
{
    foo();
    return 0;
}
  • strip なし(ARMV5)
# ./backtrace
backtrace.c(163) foo: called
backtrace.c(156) bar: called
  /root/backtrace(PrintBacktrace+0x4c)[0x12ce4] at /path/to/backtrace/backtrace.c:129
  /root/backtrace(bar+0x24)[0x12eec] at /path/to/backtrace/backtrace.c:157
  /root/backtrace(foo+0x24)[0x12f28] at /path/to/backtrace/backtrace.c:164
  /root/backtrace(main+0x1c)[0x12f5c] at /path/to/backtrace/backtrace.c:170
  /lib/libc.so.1(__uClibc_main+0x1fc)[0xb6f8972c]
  • strip あり(X86_64)
backtrace.c(163) foo: called
backtrace.c(156) bar: called
  ./backtrace(PrintBacktrace+0x54)[0x400ecd]
  ./backtrace(bar+0x27)[0x4010c6]
  ./backtrace(foo+0x27)[0x4010f4]
  ./backtrace(main+0x14)[0x40110f]
  /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5)[0x7f1e5e7ebec5]

このログ情報をハンドリングして良い方法で保存すればコンソールがデバッグログであふれることはなくなる、、、のかな?

Google 製の新しい圧縮アルゴリズム「 Brotli 」を使ってみた

googledevjp.blogspot.jp

qiita.com

最近は、組み込みでもCPU性能が上がっているため圧縮の要件も増えてきました。

zlibの組み込みがメインですが、他の圧縮方式も検討に加えられるか組み込みの観点で確認してみました。(圧縮率・時間の比較は他の記事に任せるとして)

brotli はGoogleがインターネット用に開発した圧縮アルゴリズムなので、比較的低負荷高圧縮な感じです。

ところが、いきなり圧縮アルゴリズムのほうが C++ で実装されていることが判明!

BSPやミドルウェアで圧縮機能は実装することが多いので、 C の実装であって欲しかった。。。(伸張アルゴリズムのほうは C 実装なのに。。。)

もう少し使ってみて、性能が良ければ圧縮アルゴリズムの C 実装も検討してみようかな。(あ、再実装しなくても、ライブラリ化して C のインタフェースを実装すればいいのか)

バックトレース in ARM(Qemu)

以前、挫折していた Linux 環境での C のバックトレース出力について、改めてトライしてみました。

ちなみに、ホスト環境(x86)ではそのものの backtrace()backtrace_symbols() といったAPIが利用できるけど、 buildroot で構築したクロスビルド環境には execinfo.h がなかったので、自力でバックトレースを取ることに。

業務で使用するログ機能の強化を考えていたのですが、スタックトレースは必須だと思っていたので、 Qemu 環境ではありますが動作して良かったです。

同じプログラムが業務で使用している開発ボードでも動作するか確認してみよう。

/**     @file           backtrace.c
 *      @brief          porting backtrace.
 *      @sa                     http://stackoverflow.com/questions/2536021/any-porting-available-of-stacktrace-for-uclibc/2536136#2536136
 */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* for Dl_info */
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include <inttypes.h>
#include <limits.h>
#include <dlfcn.h>
#include <errno.h>

#include "log.h"


extern void *__libc_stack_end;


struct backtrace_frame {
        void *fp;
        void *sp;
        void *lr;
        void *pc;
};


#define lengthof(array) (sizeof(array)/sizeof((array)[0]))


size_t Backtrace(void **frames, size_t size)
{
        void *top_frame_p;
        void *current_frame_p;
        struct backtrace_frame *frame_p;
        size_t frame_count;

        top_frame_p     = __builtin_frame_address(0);
        current_frame_p = top_frame_p;
        frame_p         = (struct backtrace_frame *) ((void **) current_frame_p - 3);
        frame_count     = 0;

        if ((current_frame_p != NULL)
            && (current_frame_p > (void *) &frame_count)
            && (current_frame_p < __libc_stack_end)) {

                while ((frame_count < size)
                       && (current_frame_p != NULL)
                           && (current_frame_p > (void *) &frame_count)
                           && (current_frame_p < __libc_stack_end)) {

                        frame_p = (struct backtrace_frame *) ((void **) current_frame_p - 3);
                        frames[frame_count] = frame_p->lr;
                        ++frame_count;
                        current_frame_p = frame_p->fp;
                }
        }

        return frame_count;
}

void PrintBacktrace(void)
{
        void *stack[128] = { NULL };
        Dl_info info;
        int i;

        if (Backtrace(stack, lengthof(stack)) == 0) {
                fprintf(stderr, "not found backtrace...\n");
                return;
        }
        for (i = 0; stack[i] != NULL; ++i) {
                if (dladdr(stack[i], &info) == 0) {
                        fprintf(stderr, "  unknown(???+???)[%p]\n", stack[i]);
                } else {
                        fprintf(stderr, "  %s(%s+%p)[%p]\n", info.dli_fname, info.dli_sname, stack[i] - info.dli_saddr, stack[i]);
                }
        }

        return;
}

int bar(void)
{
        printf("%s(%d) %s: called\n", __FILE__, __LINE__, __func__);
        PrintBacktrace();
        return 0;
}

int foo(void)
{
        printf("%s(%d) %s: called\n", __FILE__, __LINE__, __func__);
        bar();
        return 0;
}

int main(int argc, char **argv)
{
        foo();
        return 0;
}
# makefile for backtrace.

TARGET := backtrace

ARCH := arm
ifeq ($(ARCH),arm)
CROSS_COMPILE := arm-buildroot-linux-uclibcgnueabi-
else
CROSS_COMPILE :=
endif
CC := $(CROSS_COMPILE)gcc
CFLAGS := -g -O0 -I.
LDFLAGS := -rdynamic -ldl
ifeq ($(ARCH),arm)
LDFLAGS += -mapcs-frame
else
LDFLAGS += -fno-omit-frame-pointer
endif

all:
        $(CC) -std=c99 $(CFLAGS) -o $(TARGET) $(TARGET).c log.c $(LDFLAGS)

clean:
        rm -rf $(TARGET)

出力結果は以下の通り。

# ./backtrace
backtrace.c(101) foo: called
backtrace.c(94) bar: called
  /root/backtrace(PrintBacktrace+0x2c)[0x88b4]
  /root/backtrace(bar+0x24)[0x898c]
  /root/backtrace(foo+0x24)[0x89c4]
  /root/backtrace(main+0x10)[0x8688]
  /lib/libc.so.1(__uClibc_main+0x1fc)[0xb6f0172c]

競技プログラミングはじめました

年末だけど、なにかあたらしいことを始めたいなぁとおもったので、以前から興味があった競技プログラムを始めてみました。

AtCoder (アットコーダー)

多くの競技プログラムサイトは英語圏のものなのですが、上記のAtCoderは完全日本語なので、「問題文が読めない!」という問題はないので安心して始められます(笑)

TopCoderなどの英語圏にもチャレンジしたいけど、まずは競技プログラムに慣れるところからということで。

競技プログラムはアルゴリズム要素が多くなるのだけれど、いままで勉強してこなかったので初歩の初歩から楽しんで取り組める感じ。

ケアレスミスもあったりして、改めて自分のプログラマーとしての実力もまだまだだということを実感しました。

他の人の回答も見られるので、学習効果は高いと思います。

仕事のプログラムは他人の解法と比べることはできませんからね。

最近の興味事情

暗号化と圧縮に興味が出てきた。(実装を理解するという意味で)

組み込みでもCPU性能やセキュリティ要件が以前より増えてきたので、この辺りの技術は抑えておきたいところ。

そのまえに、アルゴリズム全般について考え方の勉強を始めたほうが良いかな?