使用Tsung对Ejabberd进行性能测试

测试环境

  • 操作系统:

    Ubuntu Server 14.04

  • Erlang版本

    OTP 17.4

  • Ejabberd版本

    15.02

依赖包

安装生成报告需要的gnuplot软件包和Template.pm Perl模块.

1
apt-get install -y gnuplot
apt-get install -y templatetoolkit-perl

命令行

编写配置文件

生成报告

tsung 会在你的Home目录~/.tsung/log中生成测试报告, 进入到报告目录执行 /usr/lib/tsung/bin/tsung_stats.pl 可在报告目录下生成HTML格式的统计信息, 可用浏览器打开查看.

例如:

1
root@scm:cd ~/.tsung/log/20150312-1407
root@scm:/usr/lib/tsung/bin/tsung_stats.pl
root@scm:~/.tsung/log/20150312-1746# ll
drwxr-xr-x 2 root root     4096 Mar 12 20:28 data/
-rw-r--r-- 1 root root    13720 Mar 12 20:32 gnuplot.log
drwxr-xr-x 2 root root     4096 Mar 12 20:28 gnuplot_scripts/
-rw-r--r-- 1 root root     7166 Mar 12 20:32 graph.html
drwxr-xr-x 2 root root     4096 Mar 12 20:28 images/
-rw-r--r-- 1 root root     1388 Mar 12 17:46 jabber_register.xml
-rw-r--r-- 1 root root  1011724 Mar 12 17:51 match.log
-rw-r--r-- 1 root root     8169 Mar 12 20:32 report.html
-rw-r--r-- 1 root root 23789099 Mar 12 17:51 tsung_controller@scm.log
-rw-r--r-- 1 root root    20601 Mar 12 17:51 tsung.log

用多个虚拟IP增大并发量

设置临时虚拟IP(重启后消失)

1
ifconfig eth1:0 192.168.8.35 netmask 255.255.255.0 up
ifconfig eth1:1 192.168.8.36 netmask 255.255.255.0 up
ifconfig eth1:2 192.168.8.37 netmask 255.255.255.0 up

http://tsung.erlang-projects.org/user_manual/conf-client-server.html#advanced-setup

1
2
3
4
5
6
7
8
9
10
<clients>
<client host="louxor" weight="1" maxusers="800">
<ip value="10.9.195.12"></ip>
<ip value="10.9.195.13"></ip>
</client>
<client host="memphis" weight="3" maxusers="600" cpu="2"/>
</clients>
<servers>
<server host="10.9.195.1" port="8080" type="tcp"></server>
</servers>

多个虚拟IP可以用于模拟多个机器. 这在负载均衡器使用客户端IP把流量分布到集群中的服务器上时特别有用. 在tsung 1.1.1中IP不再是强制要求了.

1.4.0中, 可以使用<ip scan="yes" value="eth0"/>扫描给定接口的所有IP别名(此例中为eth0)

Even if an Erlang VM is now able to handle several CPUs (erlang SMP), benchmarks shows that it’s more efficient to use one VM per CPU (with SMP disabled) for tsung clients. Only the controller node is using SMP erlang. Therefore, cpu should be equal to the number of cores of your nodes. If you prefer to use erlang SMP, add the -s option when starting tsung (and don’t set cpu in the config file).

默认, 负载均匀分地布在所有CPU上(默认每客户端一个CPU). 权重(weight)参数(整数)可用于分布客户端机器的负载比例.

例如, 如果一个客户端具有权重为1,其他客户端有权重为2, 那么后者启动的用户数为前者的两倍(比例将为1/3和2/3), 在先前的例子中, 第二个客户端又2个CPU,权重为3, 每CPU的权重为1.5

分布式测试

分布式测试

1
<client host="t1" cpu="8" maxusers="100000"></client>
<client host="t2" cpu="8" maxusers="100000"></client>
<client host="t3" cpu="8" maxusers="100000"></client>

http://my.oschina.net/jielucky/blog/168320

分布式测试的关键

  • 相同的Erlang Cookie
  • 控制器主机(Master)能够无密码登陆到(Slave)机器上, Master/Slave的机器必须都要安装Tsung
  • 配置文件中的<client host="hostname">, 其中属性host必须是名称而不能是IP地址, 因此要么在/etc/hosts添加相应的解析条目, 要么直接使用DNS

关于后端使用MySQL数据库的情况

  • users表调整为MEMORY引擎
  • 修改s2c_shaper
1
shaper:
  normal: 10000
  fast: 50000
1
c2s_shaper:
  admin: none
  all: fast
1
listen:
  -
    port: 5222
    module: ejabberd_c2s
    max_stanza_size: 65536
    shaper: c2s_shaper
    access: c2s
    zlib: true

参考资料

  1. http://tsung.erlang-projects.org/user_manual/reports.html
  2. http://blog.chinaunix.net/uid-13189580-id-3049461.html
  3. http://my.oschina.net/jielucky/blog/168320
  4. http://stackoverflow.com/questions/23011544/simulating-online-user-in-ejabberd-with-tsung
  5. http://www.quora.com/Ejabberd-load-test-with-tsung
  6. http://vidorsolutions.blogspot.jp/2010/12/load-testing-ejabberd-xmpp-server-with.html
  7. https://pdincau.wordpress.com/2010/08/01/testing-your-xmpp-external-component-using-tsung-and-ejabberd-part-2/
  8. http://cryolite.iteye.com/blog/378758

thrift_ex

如果用mix构建Erlang项目, 使用mix thrift.thrift文件生成Erlang代码. 生成的代码保存在src/gen-erl目录中. 还可以在lib/ex_gen生成Elixir包装.

(译) Ejabberd加入了Elixir的变革

Ejabberd加入了Elixir的变革

原文:

https://blog.process-one.net/ejabberd-joins-the-elixir-revolution/

前提条件

安装Erlang R17

克隆Ejabberd源码添加Elixir支持.

1
git clone git@github.com:processone/ejabberd.git
cd ejabberd
chmod +x autogen.sh
./autogen.sh
./configure --prefix=$HOME/my-ejabberd --enable-elixir
make && make install
1
cd $HOME/my-ejabberd/
./sbin/ejabberdctl live

(ejabberd@localhost)1> m('Elixir.Enum').
Module 'Elixir.Enum' compiled: Date: January 24 2015, Time: 16.27
Compiler options:  [debug_info]
Object file: /Users/mremond/my-ejabberd/lib/ejabberd/ebin/Elixir.Enum.beam
...

用Elixir编写ejabberd插件

现在可以用Elixir来编写插件, 并注册钩子扩展ejabberd的功能.

*.ex插件文件放到ejabberd的lib目录中, 让编译链知道如何编译它们.

在ejabberd的lib/目录中,添加如下文件mod_presence_demo.ex:

1
2
3
4
5
6
7
8
9
defmodule ModPresenceDemo do
@behaviour :gen_mod
def start(_host, _opts) do
:ok
end
def stop(_host) do
:ok
end
end

这是一个最小的ejabberd模块, 为了演示, 我们现在忽略hostoptions.

当你在Shell中敲入make, 模块应该能够正确的构建. 当执行make install时, 它会被安装到正确的位置.

现在可以在ejabberd配置文件ejabberd.yml中添加模块配置了, 向下面这样:

1
modules:
...
  ModPresenceDemo: {}

你可以直接使用模块的名称, Ejabberd会检测到这是一个Elixir模块,并能够正确的使用它.

下面来扩展一下这个模块的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
defmodule ModPresenceDemo do
import Ejabberd.Logger # this allow using info, error, etc for logging
@behaviour :gen_mod
def start(host, _opts) do
info('Starting ejabberd module Presence Demo')
Ejabberd.Hooks.add(:set_presence_hook, host, __ENV__.module, :on_presence, 50)
:ok
end
def stop(host) do
info('Stopping ejabberd module Presence Demo')
Ejabberd.Hooks.delete(:set_presence_hook, host, __ENV__.module, :on_presence, 50)
:ok
end
def on_presence(user, _server, _resource, _packet) do
info('Receive presence for #{user}')
:none
end
end

当启动ejabberd的时候, 应该能够在日志中看到如下输出:

1
15:17:58.913 [info] Starting ejabberd module Presence Demo test

And anytime an XMPP client changes its presence, you should see the following in the log file:

任何时候, 如果客户端改变其在线状态(presence), 你可以看到如下输出:

1
15:30:01.266 [info] Receive presence for test

参考资料

  1. https://blog.process-one.net/elixir-sips-ejabberd-with-elixir-part-1

修订

  • 2015-03-18
    • 添加参考Elixir Sips的视频讲解连接

HTTP/2 尝鲜

最近发布了HTTP/2的正式标准, 下面通过简单几个步骤尝尝鲜.

软件要求:

  • Firefox/36.0 (36.0正式支持HTTP/2)
  • H20/1.0

编译和安装

1
apt-get install -y cmake
cd /tmp
git clone https://github.com/h2o/h2o.git
cd h2o
cmake -DCMAKE_INSTALL_PREFIX=/usr/local .
make
sudo make install
  • 启动
1
h2o -c examples/h2o/h2o.conf
  • 打开Firefox在地址栏中输入:
1
# HTTP
http://192.168.8.200:8080
# HTTPS
https://192.168.8.200:8081

It works

参考资料

  1. HTTP/2服务器实现清单
    https://github.com/http2/http2-spec/wiki/Implementations

使用RefactorErl重构你的Erlang模块和应用程序

文章目录
  1. 1.
  2. 2. 简介
  3. 3. 编译
    1. 3.1. 安装Yaws Web服务器
    2. 3.2. 安装graphviz
  4. 4. 编译RefactorErl
  5. 5. 运行
  6. 6. 参考资料

本文是一篇介绍如何使用工具重构Erlang程序的文章. 本文以分析,阅读和重构Ejabberd 14.12作为示例, 演示如何重构一个现有的Erlang模块, 和应用程序.

本文的演示系统是基于Ubuntu 14.04, 并且带图形界面安装, 后续需要用到的重构工具都需要图形界面, 实际操作前, 请准备好相关的系统环境.

不建议使用非Linux进行本文的实验, 因为非Linux或多或少有一些软件兼容性问题, 会给实验过程带来不必要的麻烦, 如果你的操作系统是非Linux, 可安装VMWare, VirtualBox等虚拟机搭建本文的重构操作需要的实验环境.

本文是一个入门的介绍性文章, 要把RefactorErl用到你的工作中, 还需要阅读大量的相关资料,理解很多概念,请参考文章末尾的参考资料.

简介

RefactorErl 是一个源代码分析和转换,重构工具, 是欧洲几个大学的联合研究项目, 写作本文的时候, RefactorErl的版本为0.9.14.09

编译

系统环境 Ubuntu 14.04

安装Yaws Web服务器

Yaws Web服务器是构建基于浏览器的重构工具所需要的, 如果需要在浏览器上体验RefactorErl的功能, 需要安装Yaws Web服务器. 官方推荐使用1.95版本的Yaws.

1
wget http://yaws.hyber.org/download/yaws-1.95.tar.gz
tar zxf yaws-1.95.tar.gz
cd yaws-1.95
# 编辑如下文件,把1250行的 `HashBin = crypto:sha(Salted),`
# 改为 `HashBin = crypto:hash(sha, Salted),` 否则会导致编译出错.
vi src/yaws_websocket.erl
./configure
make
make install

安装graphviz

生成模块依赖图需要使用到graphviz

1
apt-get install -y graphviz

编译RefactorErl

1
wget http://plc.inf.elte.hu/erlang/dl/refactorerl-0.9.14.09.tar.gz
tar zxf refactorerl-0.9.14.09.tar.gz
cd refactorerl-0.9.14.09
make

运行

进入RefactorErl Shell执行各种分析操作.

1
# -db 参数标识使用的数据库引擎
# 目前支持 Mnesia, C++ graph based 和 KyotoCabinet based graph 三种存储后端
bin/referl -db kcmini

在RefactorErl Shell中设置用于浏览器分析界面的登陆密码

referl_ui_web2:set_web2_pass("admin", "admin").

命令行方式直接启动Web UI

bin/referl -web2 -yaws_path /usr/local/lib/yaws/ebin -yaws_listen 192.168.8.132 -yaws_port 8001

SHELL方式启动

1
ri:start_web2([
    {yaws_path,"/usr/local/lib/yaws/ebin"},
    {yaws_listen,"192.168.8.132"},
    {yaws_name , "192.168.8.132"},
    {yaws_port , "8001"},
    {browser_root , "/tmp/erlang"},
    {images_dir , "/tmp/graph_images"},
    {restricted_mode , true }
]).

在浏览器中打开: http://192.168.8.132:8001/

停止

ri:stop_web2().
  • 启动RefactorErl
1
bin/referl -db nif
  • 设置Web2密码
1
referl_ui_web2:set_web2_pass("admin", "admin").
  • 添加文件到数据库引擎
1
ri:add("/root/sources/ejabberd/deps/p1_xml/include/xml.hrl").
ri:add("/root/sources/ejabberd/deps/esip/include/esip.hrl").
ri:add("/root/sources/ejabberd/include/jlib.hrl").
ri:add("/root/sources/ejabberd/include/adhoc.hrl").
ri:add("/root/sources/ejabberd/include/ejabberd_config.hrl").
ri:add("/root/sources/ejabberd/include/ejabberd.hrl").
ri:add("/root/sources/ejabberd/include/ejabberd_web_admin.hrl").
ri:add("/root/sources/ejabberd/include/ELDAPv3.hrl").
ri:add("/root/sources/ejabberd/include/jlib.hrl").
ri:add("/root/sources/ejabberd/include/mod_muc_room.hrl").
ri:add("/root/sources/ejabberd/include/mod_proxy65.hrl").
ri:add("/root/sources/ejabberd/include/ns.hrl").
ri:add("/root/sources/ejabberd/include/XmppAddr.hrl").
ri:add("/root/sources/ejabberd/include/ejabberd_commands.hrl").
ri:add("/root/sources/ejabberd/include/ejabberd_ctl.hrl").
ri:add("/root/sources/ejabberd/include/ejabberd_http.hrl").
ri:add("/root/sources/ejabberd/include/eldap.hrl").
ri:add("/root/sources/ejabberd/include/http_bind.hrl").
ri:add("/root/sources/ejabberd/include/logger.hrl").
ri:add("/root/sources/ejabberd/include/mod_privacy.hrl").
ri:add("/root/sources/ejabberd/include/mod_roster.hrl").
ri:add("/root/sources/ejabberd/include/pubsub.hrl").
ri:add("/root/sources/ejabberd/src").
  • 启动Web2
1
ri:start_web2([
    {yaws_path,"/usr/local/lib/yaws/ebin"},
    {yaws_listen,"192.168.8.102"},
    {yaws_name , "192.168.8.102"},
    {yaws_port , "8001"},
    {browser_root , "/tmp/erlang"},
    {images_dir , "/tmp/graph_images"},
    {restricted_mode , true }
]).
  • 打开连接

http://192.168.8.132:8001

  • 登陆Web2分析界面

用户名: admin
密码: admin

参考资料

  1. 视频介绍
    https://erlangcentral.org/erlang-factory-2014-refactorerl-supports-your-daily-work
  2. 项目首页
    http://plc.inf.elte.hu/erlang/index.html
  3. 用户手册(PDF)
    http://plc.inf.elte.hu/erlang/dl/manual_12_01.pdf

如何使用PM2部署一个Node.js项目

创建一个新项目用于部署

第一步我们需要创建一个项目

1
mkdir pm2_test && cd pm2_test
npm init

然后创建初始化一个Express项目

1
express init

第三步安装依赖模块

1
npm install

现在一个基本的Express项目就创建好了.

初始化部署配置文件

执行

1
pm2 ecosystem

会在当前目录下生成一个ecosystem.json5文件

重命名

1
mv ecosystem.json5 ecosystem.json

编辑ecosystem.json, 设置几个选项

1
...
deploy : {
  production : {
    user : "root",            // 登陆用户名
    host : "servername",      // 要部署的目标服务器IP地址或域名
    ref  : "origin/master",   // 用于部署的Git仓库分支
    repo : "https://github.com/developerworks/pm2_test.git",  // Git仓库位置
    path : "/var/www/production", // 部署目标服务器文件系统位置
    "post-deploy" : "pm2 startOrRestart ecosystem.json --env production"  // 部署后启动
  },
}
...

执行部署

1
pm2 deploy ecosystem.json production

更新部署

1
pm2 deploy production update

参考资料

  1. https://github.com/Unitech/PM2/blob/development/ADVANCED_README.md

复制公匙到服务器的两种方式

第一种

在有ssh-copy-id工具的系统上, 比如Linux

1
ssh-copy-id username@domain

第二种

在没有ssh-copy-id工具的系统上, 比如Mac OS X

1
cat ~/.ssh/id_rsa.pub | ssh username@domain "cat >> ~/.ssh/authorized_keys"

Etco 简介

文章目录
  1. 1. Ecto 简介
  2. 2. 主要组件
    1. 2.1. Ecto 库
    2. 2.2. Ecto 模型
    3. 2.3. 查询(Query)
    4. 2.4. 移植(Migration)
  3. 3. 创建项目
    1. 3.1. 安装Ecto
    2. 3.2. 添加库(Repository)
    3. 3.3. 添加模型(Models)
    4. 3.4. 生成移植(Migration)脚本
    5. 3.5. 移植
    6. 3.6. 实际操作
  4. 4. 总结
  5. 5. 参考资料

Ecto 简介

Ecto是一种领域语言(DSL), 用于在Elixir中编写数据库查询,和其他数据库操作. Ecto是用Elixir语言开发的一个关系型数据库工具.

本章主要介绍如何在Elixir项目中使用Ecto

主要组件

Ecto有三个主要组件: 库(Repositories), 模型(Models), 和查询(Queries)

Ecto 库

库是一个数据库的封装, 可以通过如下方式定义一个库:

1
defmodule Repo do
    use Ecto.Repo, adapter: Ecto.Adapters.Postgres
    def conf do
        parse_url "ecto://username:password@localhost/ecto_simple"
    end
end

Ecto当前仅支持Postgresql数据库, 未来会支持其他数据库.

在Ecto中每个库通过定义一个start_link/0函数, 该函数需要在使用库(Repository)之前调用. 该函数不会直接调用, 而是通过一个supervisor链.
找到supervisor.ex并使用下面的init/1函数启动一个supervisor:

1
def init([]) do
    tree = [worker(Repo, [])]
    supervise(tree, strategy: :one_for_all)
end

Ecto 模型

模型是用于定义在查询,验证和回调中使用的模式(Schema), 下面是一个模式定义的例子:

1
defmodule Weather do
    use Ecto.Model
    # weather is the DB table
    schema "weather" do
        field :city,    :string
        field :temp_lo, :integer
        field :temp_hi, :integer
        field :prcp,    :float, default: 0.0
    end
end

还可以定义primary_key, foreign_key belongs_to等. 模式的详细信息可查看文档.

定义了模式后, Ecto自动地定义一个包含模式字段的结构:

1
weather = %Weather{temp_lo: 30}
weather.temp_lo #=> 30

模型和数据库交互:

1
weather = %Weather{temp_lo: 0, temp_hi: 23}
Repo.insert(weather)

插入数据库, 返回一个weather的拷贝, 可以通过返回的这个值从数据库中读取一个结构:

1
# 读取数据
weather = Repo.get Weather, 1
#=> %Weather{id: 1, ...}
# 更新
weather = %{weather | temp_lo: 10}
Repo.update(weather)
#=> :ok
# 删除
Repo.delete(weather)

查询(Query)

最后一个组件让我们可以编写查询,并向Repository执行查询.

1
import Ecto.Query
query = from w in Weather,
        where w.prcp > 0 or w.prcp == nil,
        select w
Repo.all(query)

移植(Migration)

Ecto还支持数据库移植SQL脚本. 为了生成一个新的移植脚本, 需要高数ecto移植脚本在哪里(哪个目录, 相对于项目根目录).

1
defmodule Repo do
    use Ecto.Repo, adapter: Ecto.Adapters.Postgres
    def priv do
        app_dir(:YOUR_APP_NAME, "priv/repo")
    end
end

然后, 可以通过mix工具生成移植脚本

1
mix ecto.gen.migration Repo create_posts

该命令会在priv/repo/migrations目录中生成一个新的文件. 需要编写一个SQL命令来创建表结构, 已经创建一个删除表的命令.

1
defmodule Repo.CreatePosts do
  use Ecto.Migration
  def up do
    [ "CREATE TABLE IF NOT EXISTS migrations_test(id serial primary key, name text)",
      "INSERT INTO migrations_test (name) VALUES ('inserted')" ]
  end
  def down do
    "DROP TABLE migrations_test"
  end
end

使用下面的命令, 运行所有PENDING状态的的移植脚本,

1
mix ecto.migrate Repo

回滚

1
mix ecto.rollback Repo --all

Ecto的更多信息可以到Github上的Ecto项目上获取, 或阅读Ecto文档

创建项目

简单介绍Ecto后, 是时候在项目中使用了. 下面创建一个名为 elixir_jobs的项目:

1
mix new elixir_jobs
cd exlixir_jobs

安装Ecto

打开 mix.exs 文件. 需要postgresql驱动作为依赖, 这里我们使用postgrex.

1
defp deps do
    [{:postgrex, "0.5.2"},
     {:ecto, "0.2.0"}
    ]
end

更新应用程序列表包含ectopostgrex.

1
def application do
    [applications: [:postgrex, :ecto]]
end

终端中运行mix deps.get获取依赖.

添加库(Repository)

A repo in ecto terms is the definition of a basic interface to a database,
in this case PostgreSQL. Open up lib/elixir_jobs/repo.ex and add the code below.
If the directory doesn’t exist, create it.

1
defmodule ElixirJobs.Repo do
    use Ecto.Repo, adapter: Ecto.Adapters.Postgres
    def conf do
        parse_url "ecto://postgresuser:password@localhost/elixir_jobs"
    end
    def priv do
        app_dir(:elixir_jobs, "priv/repo")
    end
end

我们定义了PostgreSQL连接URL.

你需要替换的时postgresuser, passwordelixir_jobs数据库名称. 我们使用了移植功能,所以priv函数是必须的.

我们只需要告诉Ecto移植脚本放在什么位置, 这个位置是priv/repo.

接下来, 我们应该确保Repo模块随我们的应用程序一起启动. 打开lib/elixir_jobs.ex:

1
defmodule ElixirJobs do
    use Application

    def start(_type, _args) do
        import Supervisor.Spec
        tree = [worker(ElixirJobs,Repo, [])]
        opts = [name: ElixirJobs.Sup, strategy: :one_for_one]
        Supervisor.start_link(tree, opts)
    end
end

确保一切OK, 让我们编译该项目

1
mix compile

如果没有错误消息, 然后我们需要使用psql工具添加数据库elixir_jobs.

1
$> psql -Upostgresuser -W template1
Password for user postgresuser:
template1=# CREATE DATABASE elixir_jobs;
CREATE DATABASE
template1=# \q

使用\q退出psql, 然后添加一个模型用于查询.

添加模型(Models)

创建一个单独的文件 lib/elixir_jobs/jobs.ex,并输入下面的代码, 模型名称为Jobs.

1
defmodule ElixirJobs.Jobs do
    use Ecto.Model

    schema "jobs" do
        field :title, :string
        field :type, :string
        field :description, :string
        field :status, :string
    end
end

运行mix help可以看到一组用于移植的命令

生成移植(Migration)脚本

我们使用 mix ecto.gen.migration生成移植脚本, 首先使用下面的命令创建一个移植脚本.

1
$> mix ecto.gen.migration ElixirJobs.Repo create_job
* creating priv/repo/migrations
* creating priv/repo/migrations/20140802043823_create_job.exs

打开生成的priv/repo/migrations/20140802043823_create_job.exs文件. 在这个文件中, 我们还必须手工编写SQL命令用于创建和删除表.

文件名20140802043823_create_job.exs依据生成的时间变化.

1
defmodule ElixirJobs.Repo.Migrations.CreateJob do
  use Ecto.Migration
  def up do
    "CREATE TABLE jobs(id serial primary key, title varchar(125), type varchar(50), description text, status varchar(50))"
  end
  def down do
    "DROP TABLE jobs"
  end
end

创建了移植脚本后, 我们就可以运行它了.

移植

运行移植非常容易, 像这样:

1
$> mix ecto.migrate ElixirJobs.Repo
* running UP _build/dev/lib/elixir_jobs/priv/repo/migrations/20140802043823_create_job.exs

就这样! 如果你打开elixir_jobs数据库, 你会发现一个jobs表, 如下:

1
 Column    |          Type          |                     Modifiers
-------------+----------------------+---------------------------------------------------
 id        | integer                | not null default nextval('jobs_id_seq'::regclass)
 title     | character varying(125) |
 type      | character varying(50)  |
 description| text                   |
 status    | character varying(50)  |

现在可以测试一下INSERT, UPDATE, DELETE以及SELECT操作了.

实际操作

启动iex测试我们的设置. 开始操作数据库之前, 需要使用start_link函数启动库(Repo), 否则会得到一个错误.

1
$> iex -S mix
Erlang/OTP 17 [erts-6.0] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (0.14.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> ElixirJobs.Repo.start_link
{:ok, #PID<0.108.0>}

如果返回:ok, 那么就可以使用定义模型时Ecto生成的结构插入数据了.

1
iex(2)> job = %ElixirJobs.Jobs{title: "Elixir Expert Needed", description: "ElixirDose need your help to produce a high quality Elixir article every month or two.", type: "Remote", status: "Part Time"}
iex(3)> ElixirJobs.Repo.insert(job)
%ElixirJobs.Jobs{description: "ElixirDose need your help to produce a high quality Elixir article every month or two.", id: 2, status: "Part Time", title: "Elixir Expert Needed", type: "Remote"}

打开jobs表, 就可以看到刚刚插入的数据. 现在来读取数据:

1
iex(4)> ejob = Repo.get(ElixirJobs.Jobs, 1)
%ElixirJobs.Jobs{description: "ElixirDose need your help to produce a high quality Elixir article every month or two.", id: 1, status: "Part Time", title: "Elixir Expert Needed", type: "Remote"}
iex(5)> ejob.title
"Elixir Expert Needed"

然后更新数据:

1
iex(6)> ejob = %{ejob | title: "Elixir Writer Needed"}
%ElixirJobs.Jobs{description: "ElixirDose need your help to produce a high quality Elixir article every month or two.", id: 1, status: "Part Time", title: "Elixir Writer Needed", type: "Remote"}
iex(7)> ElixirJobs.Repo.update(ejob)
:ok

验证数据已经被更新:

1
iex(8)> ElixirJobs.Repo.get(Jobs, 1)
%ElixirJobs.Jobs{description: "ElixirDose need your help to produce a high quality Elixir article every month or two.", d: 1, status: "Part Time", title: "Elixir Writer Needed", type: "Remote"}

现在, 删除数据:

1
iex(9)> ElixirJobs.Repo.delete(ejob)
:ok
iex(10)> ElixirJobs.Repo.get(Jobs, 1)
nil

总结

本文涵盖了Ecto的基本使用, 用DSL编写查询,以及和数据库交互.

参考资料

  1. https://github.com/elixir-lang/ecto
  2. http://www.youtube.com/watch?v=SJRfujy9vLA
  3. http://elixirsips.com/episodes/024_ecto_part_1.html

使用iconutil命令行工具制作App图标

iconutil是一个命令行工具, 用于在.iconset文件(实际上是一个图标集目录)和.icns文件之间转换.

用到的图片可以从这里下载:

https://github.com/andrevvm/appicns

比如下面的命令, 把一个1024x1024netbook.icns文件

iconutil -c iconset -o output.iconset netbook.icns

这个命令很简单,可以通过manpage查看:

man iconutil

语法:

iconutil -c {icns | iconset} [-o file] file

生成的图标集效果

图标集

参考资料

  1. http://stackoverflow.com/questions/12306223/how-to-manually-create-icns-files-using-iconutil

Erlang Ranch - 监听器

Ranch 是一个Erlang Tcp服务器Acceptor池的实现.

监听器是一组进程, 其角色是在端口上监听新连接. 它管理一个Acceptor进程池,每个Acceptor阻塞,知道有新的连接请求到达

ElixirSublime 代码补全功能的异常问题.

最开始的问题是:

1
Traceback (most recent call last):
  File "/Applications/Sublime Text.app/Contents/MacOS/sublime_plugin.py", line 358, in on_query_completions
    res = callback.on_query_completions(v, prefix, locations)
  File "/Users/user/Library/Application Support/Sublime Text 3/Packages/ElixirSublime/elixir_sublime.py", line 270, in on_query_completions
    if not session.send('COMPLETE', expand_selection(view, locations[0], aliases=aliases)):
  File "/Users/user/Library/Application Support/Sublime Text 3/Packages/ElixirSublime/elixir_sublime.py", line 189, in send
    self.socket.send(str.encode(cmd))
AttributeError: 'NoneType' object has no attribute 'send'
Traceback (most recent call last):
  File "/Applications/Sublime Text.app/Contents/MacOS/sublime_plugin.py", line 311, in on_activated_async
    callback.on_activated_async(v)
  File "/Users/user/Library/Application Support/Sublime Text 3/Packages/ElixirSublime/elixir_sublime.py", line 255, in on_activated_async
    self.on_load_async(view)
  File "/Users/user/Library/Application Support/Sublime Text 3/Packages/ElixirSublime/elixir_sublime.py", line 260, in on_load_async
    ElixirSession.ensure(os.path.dirname(filename))
  File "/Users/user/Library/Application Support/Sublime Text 3/Packages/ElixirSublime/elixir_sublime.py", line 160, in ensure
    session.connect()
  File "/Users/user/Library/Application Support/Sublime Text 3/Packages/ElixirSublime/elixir_sublime.py", line 179, in connect
    self.socket, _ = _socket.accept()
  File "./socket.py", line 135, in accept
socket.timeout: timed out

ElixirSublime日志

1
<_io.TextIOWrapper name='/var/folders/m3/vn1yyrfx7l36cq6p8msc6wt80000gn/T/ElixirSublime.log' mode='w' encoding='US-ASCII'>

通过Sublime的日志文件看到ElixirSublime使用了一个Mix项目~/Library/Application Support/Sublime Text 3/Packages/ElixirSublime/sublime_completion,
其中需要的依赖poison没找到, 我就进入这个执行了一下:

1
mix deps.get

结果又冒出一个证书问题.

hex.pm的证书不可信

上述问题, 请参考Elixir 在Mac OS X上安装Hex.pm服务器证书不信任的问题

Elixir ExUnit测试

Mix的测试过程

  • Mix首先启动应用程序
  • Mix然后查找项目根目录下的test子目录, 查找模式为test/**/_test.exs, 所有匹配这个模式的Elixir脚本都被认为是测试代码.

给测试方法打标签

有的时候,为了加速测试过程,我们需要过滤那些依赖外部资源的测试,为此,我们可以给测试打上标签,排除那些标记为ignore的测试.

首先需要在测试配置文件test/test_helper.exs中添加如下一行,用于排除外部测试

1
ExUnit.configure exclude: [ignore: true]

不依赖外部资源的代码都通过测试后, 如果你还需要做一次整体测试, 可以通过参数, 把标记为ignore,值为true的测试包含进来.

1
mix test --include ignore:true

也可以指定所有包含标签ignore的测试,而不管其值是什么

1
mix test --include ignore

配置测试

  • :test_paths 指定测试代码的位置列表, 默认为["test"], 每个测试路径下面都应该包含一个test_helper.exs文件.
  • :test_pattern 测试匹配模式, 默认为*_test.exs
  • :test_coverage 测试覆盖率选项

给测试打标记

给测试打上@ignore标记

1
mix test --trace --exclude ignore:true

运行结果

关于测试的详细配置和说明,请参考mix help test

参考资料

  1. http://stackoverflow.com/questions/26150146/how-can-i-make-mix-run-only-specific-tests-from-my-suite-of-tests

Erlang HiPE选项

  • 在编译期间输出内部调试信息

    1
    4> hipe:help_option(debug).
    debug - Outputs internal debugging information during compilation
  • 自动加载产生的原生代码到内存中

    1
    5> hipe:help_option(load).
    load - Automatically load the produced native code into memory
  • Displays assembly listing with addresses and bytecode

    1
    6> hipe:help_option(pp_asm).
    pp_asm - Displays assembly listing with addresses and bytecode
    Currently available for x86 only
  • 显示输入BEAM代码

    1
    7> hipe:help_option(pp_beam).
    pp_beam - Display the input BEAM code
  • 显示中间HiPE-ICode代码

    1
    8> hipe:help_option(pp_icode).
    pp_icode - Display the intermediate HiPE-ICode
  • 显示生成的(后端相关)原生代码

    1
    9> hipe:help_option(pp_native).
    pp_native - Display the generated (back-end specific) native code
  • 显示中间HiPE-RTL代码

    1
    10> hipe:help_option(pp_rtl).
    pp_rtl - Display the intermediate HiPE-RTL code
  • 报告编译器不同阶段的编译时间

    1
    12> hipe:help_option(time).
    time - Reports the compilation times for the different stages
    of the compiler.
        {time, Module}       reports timings for the module Module.
        特定模块
        {time, [M1, M2, M3]} reports timings for the specified modules.
        指定模块列表
        {time, all}          reports timings all modules.
        所有模块
        time                 reports timings for the main module.
        主模块
  • 指定编译时间限制,单位毫秒, 必须为非负整数, 或原子’infinity’, 当前默认限制为15分钟(900000毫秒)

    1
    13> hipe:help_option(timeout).
    timeout - Specify compilation time limit in ms. Used as {timeout, LIMIT}.
        The limit must be a non-negative integer or the atom 'infinity'.
        The current default limit is 15 minutes (900000 ms).
  • 输出完成了什么

    1
    14> hipe:help_option(verbose).
    verbose - Output information about what is being done

Elixir 把Erlang代码转换为Elixir

项目地址:

https://github.com/developerworks/ws_cowboy

项目描述文件mix.exs

1
defmodule WsCowboy.Mixfile do
  use Mix.Project
  def project do
    [app: :ws_cowboy,
     version: "0.0.1",
     elixir: "~> 1.0",
     deps: deps]
  end
  def application do
    [
      applications: [:logger, :cowboy],
      mod: {WsCowboy, []}
    ]
  end
  defp deps do
    [{:cowboy,"~> 1.0.0"}]
  end
end

Application

Application

Supervisor

Supervisor

Websocket Handler

Websocket Handler

运行结果

运行结果

参考资料

  1. Converting Erlang code into Elixir
    http://blog.plataformatec.com.br/2014/11/converting-erlang-code-into-elixir/

Elixir提示 001

模块

  • Elixir代码可以组织到模块中
  • 模块是函数的集合
  • 模块是最小编译单元: 每个模块经过编译会生成一个.beam文件
  • 通常在一个Elixir源文件中只定义一个模块,但也可以在一个源文件中定义多个模块,不管在源文件中有多少个模块,反正每个模块都会生成一个.beam文件,这种情况是一个编译输入文件产生多个编译输出文件

unless语句

unless语句为if语句的反义

举例

1
defmodule Test1 do
    def run do
        if true do
            IO.puts "true"
        else
            IO.puts "false"
        end
    end
end

当表达式1+1==2为真时, 执行if…end之间的代码

1
defmodule Test2 do
    def run do
        unless true do
            IO.puts "true"
        else
            IO.puts "false"
        end
    end
end

请把Test1和Test2模块代码复制粘贴到IEx中观察却别

参考资料

  1. http://stackoverflow.com/questions/25414347/how-do-i-run-a-beam-file-compiled-by-elixir-or-erlang

Erlang 用erlang.mk和relx发布Erlang应用程序(译)

发布OTP应用程序一直是个困难的任务. 像reltool,rebar这类工具虽然简化了这类问题,但并没有解决所有问题.本文展示了一个候选方案,希望是一个更简化的方案.

发布OTP应用程序需要两个步骤.

  • 首先需要构建各种需要包含在发布包中的各种OTP应用程序.
  • 构建完成后, 把Erlang运行时系统,节点启动脚本,应用程序配置文件打包为一个可发布的软件包.

erlang.mk解决了第一个步骤的问题.它是一个GNU Make包含文件. 只需要把它包含在Makefile文件中即可构建项目,获取和构建依赖,构建文档,执行静态分析等任务.

relx 解决了第二个步骤的问题.它是一个发布创建工具, 用于把应用程序打包为单个可执行程序. 它并不要求配置文件, 如果你需要,那也是很小的一个配置文件.

来看一个最简单的使用erlang.mk的Makefile文件. 其只需要做一件事: 定义项目名称.

PROJECT = my_peojct

include erlang.mk

只需要这么定义就可以让你使用make命令来构建项目, 以及使用make tests运行测试. 如果使用ErlyDTL,它还可以构建templates目录中的*.dtl模板.

PROJECT = ninenines

DEPS = cowboy erlydtl
dep_cowboy = https://github.com/extend/cowboy.git 0.8.5
dep_erlydtl = https://github.com/evanmiller/erlydtl.git 4d0dc8fb

.PHONY: release clean-release

release: clean-release all projects
    relx -o rel/$(PROJECT)

clean-release: clean-projects
    rm -rf rel/$(PROJECT)

include erlang.mk

其中我们看到了如何定义依赖DEPS = cowboy erlydtl:

  • 首先,列举所有的依赖名称
  • 其次,单独定义每一个依赖的URL,提交号,标记或分支
  • 随后,定义了两个目标, release为默认目标, 因为它是最先定义的. 你可以覆盖默认目标all,其作用是构建应用程序与其依赖.
  • release目标使用relxrel/ninenines/目录构建发布.

下面来看一下构建此发布的配置文件.

{release, {ninenines, "1"}, [ninenines]}.

{extended_start_script, true}.
{sys_config, "rel/sys.config"}.

{overlay, [
    {mkdir, "log"},
    {copy, "rel/vm.args",
        "releases/\{\{release_name\}\}-\{\{release_version\}\}/vm.args"}
]}.

第一行定义了一个名为ninenines的发布,其又一个版本号1, 包含一个应用程序,名称也为ninenines.

We then use the extended_start_script option to tell relx that we would like to have a start script that allows us to
not only start the release, but do so with the node in the background, or also to allow us to connect to a running node, and so on.
This start script has the same features as the one tools like rebar generates.

然后使用extended_start_script告知relx我们想要有一个启动脚本用于启动应用程序, …..
该启动脚本和rebar工具生成的作用是一样的.

参考资料

  1. Build Erlang releases with erlang.mk and relx
    http://ninenines.eu/articles/erlang.mk-and-relx/
  2. erlang.mk Github项目
    https://github.com/ninenines/erlang.mk

Elixir发布管理器 Exrm

Elixir发布管理器(Elixir Release Manager)是一个Elixir任务模块,用于把一个完整Elixir项目打包为一个.tar.gz文件用于发布和部署.

首先, 需要在mix.exs文件中添加如下依赖:

1
defp deps do
    [{:exrm, "~> 0.14.16"}]
end

下载依赖,并编译

1
mix deps.get
mix deps.compile

发布项目

1
mix release
Build a release for the current mix application.

Examples

┃ # Build a release using defaults
┃ mix release
┃
┃ # Pass args to erlexec when running the release
┃ mix release --erl="-env TZ UTC"
┃
┃ # Enable dev mode. Make changes, compile using MIX_ENV=prod
┃ # and execute your release again to pick up the changes
┃ mix release --dev
┃
┃ # Set the verbosity level
┃ mix release --verbosity=[silent|quiet|normal|verbose]

You may pass any number of arguments as needed. Make sure you pass arguments
using --key=value, not --key value, as the args may be interpreted incorrectly
otherwise.

Elixir 正则表达式

文章目录
  1. 1. 定义正则表达式
  2. 2. 验证手机号码
  3. 3. 验证电子邮件地址
  4. 4. 验证身份证号码
  5. 5. 参考资料

定义正则表达式

  • 在Elixir中定义正则表达式需要使用到一个特殊的前缀~r
  • 匹配操作需要使用操作符=~

验证手机号码

比如我们定义一个验证11为手机号码的正则表达式,启动Elixir Shell, 定义一个手机号码的正则表达式:

1
iex(1)> regex_cellphone = ~r"^[0-9]{11,11}$"
~r/^[0-9]{11,11}$/

匹配:

1
iex(2)> "13912345678" =~ regex_cellphone
true

在判断条件中使用

1
iex(7)> cond do
...(7)>   "13912345678" =~ regex_cellphone == true ->
...(7)>     IO.puts "Cellphone number 13912345678 is a valid cell phone number"
...(7)>   true ->
...(7)>     false
...(7)> end
Cellphone number 13912345678 is a valid cell phone number
:ok

在关键业务中, 还需要通过短信验证手机号的有效性, 上述验证并不能保证该号码是一个可用的手机号码.

验证电子邮件地址

1
iex(13)> str_email = "riza@elixirdose.com"
"riza@elixirdose.com"
iex(14)> Regex.match?(~r/(\w+)@([\w.]+)/, str_email)
true
iex(15)> cond do
...(15)>   Regex.match?(~r/(\w+)@([\w.]+)/, str_email) == true ->
...(15)>    IO.puts "Your email is a valid email"
...(15)>   true ->
...(15)>    IO.puts "Your email address is invalid"
...(15)> end
Your email is a valid email
:ok

编译版本, 正则表达式有几个变化

  • Regex.compile编译正则表达式字符串不需要使用~r前缀
  • \符号需要使用\\替代(转义)
    上面在Elixir Shell中的正则表达式~r/(\w+)@([\w.]+)/需要改成字符串(\\w+)@([\\w.]+), 对应的编译语句为{:ok, regex} = Regex.compile("(\\w+)@([\\w.]+)")
1
defmodule RegExpTest do
    def run(email_address) do
        case Regex.compile("(\\w+)@([\\w.]+)") do
            {:ok, regex} ->
                if Regex.match?(regex, email_address) == true do
                    IO.puts "Your email is a valid email"
                else
                    IO.puts "Your email address is invalid"
                end
            _ ->
                IO.puts "Invalid regex definition"
        end
    end
end

在Elixir Shell中执行:

1
iex(4)> defmodule RegExpTest do
...(4)>     def run(email_address) do
...(4)>         case Regex.compile("(\\w+)@([\\w.]+)") do
...(4)>             {:ok, regex} ->
...(4)>                 if Regex.match?(regex, email_address) == true do
...(4)>                     IO.puts "Your email is a valid email"
...(4)>                 else
...(4)>                     IO.puts "Your email address is invalid"
...(4)>                 end
...(4)>             _ ->
...(4)>                 IO.puts "Invalid regex definition"
...(4)>         end
...(4)>     end
...(4)> end
iex:4: warning: redefining module RegExpTest
{:module, RegExpTest,
 <<70, 79, 82, 49, 0, 0, 6, 160, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 122, 131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115, 95, 118, 49, 108, 0, 0, 0, 2, 104, 2, ...>>,
 {:run, 1}}
iex(5)> RegExpTest.run("riza@elixirdose.com")
Your email is a valid email
:ok

验证身份证号码

规则:

  • 身份证号码必须是18为字符长度,有17为数字本体码和1位校验码构成
  • 本体码包含3个部分, 分别是
    • 地址(地区)码
    • 出生日期
    • 顺序码(男性奇数, 女性偶数)
      在特定应用下,比如需要用户输入身份证号码的应用场景, 我们可以通过顺序吗识别用户的性别,减少用户输入.

TODO:: 实现

参考资料

  1. 验证邮件地址示例
    https://gist.github.com/rizafahmi/b1711f7f10aaf7a7ced9
  2. 身份证验证规则
    http://wenku.baidu.com/link?url=lSfaWKLcnVJTF7LYXOG5qdEhY_Ns5mBWyPu372WcskB7aII2UTzQ5UakYCIBENBy2LKTz0HftEr7boaFnFZcjjbHec_8Py708uqq9nfMmEW
  3. 常用语法,控制流语句
    http://learnxinyminutes.com/docs/zh-cn/elixir-cn/