<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-6265608756663924839</id><updated>2011-12-30T12:24:05.061-08:00</updated><category term='Automake'/><category term='distributed filesystem'/><category term='Mnesia'/><category term='fuse'/><category term='hot code loading'/><category term='Erlang'/><title type='text'>Dukes of Erl</title><subtitle type='html'>Someday the mountain might get &amp;rsquo;em, but the law never will.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Michael Radford</name><uri>http://www.blogger.com/profile/16558736208373025619</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>35</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-6622961196913363729</id><published>2010-01-14T12:02:00.000-08:00</published><updated>2010-01-14T12:41:28.415-08:00</updated><title type='text'>Minor Erlang Interface Tricks</title><content type='html'>erlrc requires that you drop a file whose name is your node name and whose contents is the node cookie into a particular directory so that the packaging system can find running Erlang VMs and ask them to do hot-code upgrades.  It's not that hard, but I figured I would put some shell scripts into the erlrc google code project demonstrating how to do this and other minor tricks that make it a little nicer to talk to an Erlang VM from the (non-Erlang) command line.&lt;br /&gt;All of these scripts are driven by a POSIX shell syntax configuration file describing an Erlang VM, which can be pretty short.  Here's one I'm using right now for my personal stuff:&lt;br /&gt;&lt;pre class="brush:bash"&gt;% cat /usr/share/myerlnode/myerlnode.rc&lt;br /&gt;node_name_file='/etc/erlrc.d/nodes/erlang'&lt;br /&gt;&lt;br /&gt;run_extra_args='+A 64 -noshell -noinput -s crypto -s mnesia -eval "case erlrc_boot:boot () of ok -&gt; ok; _ -&gt; init:stop () end" &gt;erlang.out 2&gt;erlang.err &amp;amp;'&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;node_name_file (the only required config setting) defines what the node name will be (via the basename).  This config file drives four scripts:&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/erlrc/source/browse/trunk/erlstart/bin/erlstart-run-erlang"&gt;erlstart-run-erlang&lt;/a&gt;: starts an Erlang VM.  doesn't do that much, really: creates the node file for erlrc, and sets the heart command in case you want to use heart.&lt;/li&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/erlrc/source/browse/trunk/erlstart/bin/erlstart-remsh"&gt;erlstart-remsh&lt;/a&gt;: starts a remote shell on an Erlang VM.&lt;/li&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/erlrc/source/browse/trunk/erlstart/bin/erlstart-eval"&gt;erlstart-eval&lt;/a&gt;: takes the argument, evals it on an Erlang VM, and prints the result to standard out.  very useful shell script glue for maintenance scripts.&lt;/li&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/erlrc/source/browse/trunk/erlstart/bin/erlstart-etop"&gt;erlstart-etop&lt;/a&gt;: runs etop on an Erlang VM.&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;If you put your config file in the default location (/etc/erlstart.rc) or export an environment variable indicating the location (ERLSTART_CONFIG_FILE) then things are pretty zero-configuration.  Doing this kind of thing at the (non-Erlang) shell gets very addictive:&lt;/div&gt;&lt;/div&gt;&lt;pre class="brush:bash"&gt;% erlstart-eval 'application:which_applications()'&lt;br /&gt;[{anwhereos,"Rest API for time series persistence and retrieval.","0.1.0"},&lt;br /&gt;{drurlyjsclientsrv,"Serve the drurly jsclient from the drurly server.",&lt;br /&gt;                "0.0.1"},&lt;br /&gt;{drurly,"Social sharing server.","2.2.0"},&lt;br /&gt;{mcedemo,"TinyMCE + Nitrogen demo.","1.3.0"},&lt;br /&gt;{inets,"INETS  CXC 138 49","5.0.12"},&lt;br /&gt;{mochiweb,"MochiWeb is an Erlang library for building lightweight HTTP servers.",&lt;br /&gt;       "0.2009.05.26"},&lt;br /&gt;{nitrogen,"Nitrogen web framework for Erlang.","0.2009.05.12.3"},&lt;br /&gt;{nitromce,"A Nitrogen element which corresponds to a TinyMCE editor instance.",&lt;br /&gt;       "4.0.1"},&lt;br /&gt;{sgte,"String template language for Erlang.","0.7.1"},&lt;br /&gt;{signzor,"Erlang library to generate signed printable encodings.","0.0.1"},&lt;br /&gt;{tcerl,"Erlang driver for tokyocabinet.","1.3.1h"},&lt;br /&gt;{webmachine,"An Erlang REST framework.","0.2009.09.24b"},&lt;br /&gt;{erlrc,"Extensible application management.","0.2.3"},&lt;br /&gt;{mnesia,"Mnesia storage API extensions.","4.4.7.6.1"},&lt;br /&gt;{crypto,"CRYPTO version 1","1.5.3"},&lt;br /&gt;{sasl,"SASL  CXC 138 11","2.1.5.4"},&lt;br /&gt;{stdlib,"ERTS  CXC 138 10","1.15.5"},&lt;br /&gt;{kernel,"ERTS  CXC 138 10","2.12.5"}]&lt;/pre&gt;erlstart-run-erlang is fairly low level, so for an init script I would do something like the following: first, a little stub installed into /etc/rc.d&lt;br /&gt;&lt;pre class="brush:bash"&gt;#! /bin/sh&lt;br /&gt;&lt;br /&gt;# chkconfig: 2345 20 80&lt;br /&gt;# description: Control my personal Erlang node.&lt;br /&gt;&lt;br /&gt;exec myerlnodectl "$@"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and then the actual guts installed as myerlnodectl&lt;br /&gt;&lt;pre class="brush:bash"&gt;#! /bin/sh&lt;br /&gt;&lt;br /&gt;eval_with_main_node () \&lt;br /&gt;{&lt;br /&gt;  erl -name myerlnodetmp$$ \&lt;br /&gt;      -hidden \&lt;br /&gt;      -setcookie "$cookie" \&lt;br /&gt;      -noshell -noinput \&lt;br /&gt;      -eval "MainNode = list_to_atom (\"$1\"), $2" \&lt;br /&gt;      -s erlang halt&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;get_hostname () \&lt;br /&gt;{&lt;br /&gt;  erl -name myerlnodetmp$$ -setcookie $$ -noshell -noinput -eval '&lt;br /&gt;    [ Host ] = tl (string:tokens (atom_to_list (node ()), "@")),&lt;br /&gt;    io:format ("~s~n", [ Host ])&lt;br /&gt;  ' -s init stop&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;id=`basename "$0"`&lt;br /&gt;&lt;br /&gt;if test -d /root&lt;br /&gt;then&lt;br /&gt;  HOME=${HOME-/root}&lt;br /&gt;else if test -d /var/root&lt;br /&gt;  then&lt;br /&gt;    HOME=${HOME-/var/root}&lt;br /&gt;  fi&lt;br /&gt;fi&lt;br /&gt;export HOME&lt;br /&gt;&lt;br /&gt;ERLSTART_CONFIG_FILE=${ERLSTART_CONFIG_FILE-/usr/share/myerlnode/myerlnode.rc}&lt;br /&gt;export ERLSTART_CONFIG_FILE&lt;br /&gt;&lt;br /&gt;. "$ERLSTART_CONFIG_FILE"&lt;br /&gt;&lt;br /&gt;cookie=${cookie-turg}&lt;br /&gt;user=${user-erlang}&lt;br /&gt;hostname=${hostname-`get_hostname`}&lt;br /&gt;node_name=`basename "$node_name_file"`&lt;br /&gt;full_name="$node_name@$hostname"&lt;br /&gt;shutdown_file=${shutdown_file-/var/run/myerlnode.shutting_down}&lt;br /&gt;&lt;br /&gt;ERL_CRASH_DUMP=${ERL_CRASH_DUMP-/dev/null}&lt;br /&gt;export ERL_CRASH_DUMP&lt;br /&gt;&lt;br /&gt;case ${1-"status"} in&lt;br /&gt;start)&lt;br /&gt;  test "`id -u`" -eq 0 || exec sudo $0 "$@"&lt;br /&gt;&lt;br /&gt;  printf "Starting Erlang... "&lt;br /&gt;&lt;br /&gt;  if test -f "$node_name_file" &amp;amp;&amp;amp;                                     \&lt;br /&gt;     test true = "`erlstart-eval 'true' 2&gt;/dev/null`" 2&gt;/dev/null&lt;br /&gt;    then&lt;br /&gt;      echo "already started."&lt;br /&gt;      exit 0&lt;br /&gt;    fi&lt;br /&gt;&lt;br /&gt;  pid=`eval_with_main_node "$full_name" '&lt;br /&gt;         io:format ("~p", [&lt;br /&gt;           case rpc:call (MainNode, os, getpid, []) of&lt;br /&gt;             { badrpc, _ } -&gt; undefined;&lt;br /&gt;             Pid -&gt; list_to_integer (Pid)&lt;br /&gt;           end ])'`&lt;br /&gt;&lt;br /&gt;  test "$pid" -gt 0 2&gt;/dev/null &amp;amp;&amp;amp; {&lt;br /&gt;    test -f "$shutdown_file" &amp;amp;&amp;amp; {&lt;br /&gt;      oldpid=`cat "$shutdown_file"`&lt;br /&gt;      test "$pid" -eq "$oldpid" &amp;amp;&amp;amp; {&lt;br /&gt;        echo "shutdown in progress (pid = '$pid')." 1&gt;&amp;amp;2&lt;br /&gt;        exit 1&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  rm -f "$shutdown_file"&lt;br /&gt;&lt;br /&gt;  # check for -s shell support&lt;br /&gt;  su -l -s /bin/sh $user -c true &gt;/dev/null 2&gt;/dev/null&lt;br /&gt;&lt;br /&gt;  if test $? = 0&lt;br /&gt;    then&lt;br /&gt;      dashs="-s /bin/sh"&lt;br /&gt;    else&lt;br /&gt;      dashs=""&lt;br /&gt;    fi&lt;br /&gt;&lt;br /&gt;  ${niceness+ nice -n $niceness}                                      \&lt;br /&gt;  su -l $dashs "$user" -c                                             \&lt;br /&gt;    "env cookie=\"$cookie\" erlstart-run-erlang \"$ERLSTART_CONFIG_FILE\""&lt;br /&gt;&lt;br /&gt;  eval_with_main_node "$full_name"                                    \&lt;br /&gt;      "Wait = fun (_, 0) -&gt;&lt;br /&gt;                    failed;&lt;br /&gt;                  (Cont, Max) -&gt;&lt;br /&gt;                    case net_adm:ping (MainNode) of&lt;br /&gt;                      pong -&gt;&lt;br /&gt;                        ok;&lt;br /&gt;                      pang -&gt;&lt;br /&gt;                        timer:sleep (100),&lt;br /&gt;                        Cont (Cont, Max - 1)&lt;br /&gt;                    end&lt;br /&gt;              end,&lt;br /&gt;       DontTellMe = 100,&lt;br /&gt;       ok = Wait (Wait, DontTellMe)" || {&lt;br /&gt;    echo "" 1&gt;&amp;amp;2&lt;br /&gt;    echo "$id: could not connect to node after 10 seconds" 1&gt;&amp;amp;2&lt;br /&gt;    exit 1&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  eval_with_main_node "$full_name"                                    \&lt;br /&gt;      "Wait = fun (_, 0) -&gt;&lt;br /&gt;                    failed;&lt;br /&gt;                  (Cont, Max) -&gt;&lt;br /&gt;                    case rpc:call (MainNode, init, get_status, []) of&lt;br /&gt;                      { started, _ } -&gt;&lt;br /&gt;                        ok;&lt;br /&gt;                      { starting, _ } -&gt;&lt;br /&gt;                        timer:sleep (100),&lt;br /&gt;                        Cont (Cont, Max - 1);&lt;br /&gt;                      { Status, _ } -&gt;&lt;br /&gt;                        { failed, Status }&lt;br /&gt;                    end&lt;br /&gt;              end,&lt;br /&gt;       DontTellMe = 100,&lt;br /&gt;       ok = Wait (Wait, DontTellMe)" || {&lt;br /&gt;    echo "" 1&gt;&amp;amp;2&lt;br /&gt;    echo "$id: node did not boot after 10 seconds" 1&gt;&amp;amp;2&lt;br /&gt;    exit 1&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;echo "done."&lt;br /&gt;;;&lt;br /&gt;&lt;br /&gt;stop)&lt;br /&gt;  test "`id -u`" -eq 0 || exec sudo $0 "$@"&lt;br /&gt;&lt;br /&gt;  printf "Stopping Erlang... "&lt;br /&gt;&lt;br /&gt;  if test ! -f "$node_name_file" ||                                   \&lt;br /&gt;     test true != "`erlstart-eval 'true' 2&gt;/dev/null`" 2&gt;/dev/null&lt;br /&gt;    then&lt;br /&gt;      echo "not running."&lt;br /&gt;      exit 0&lt;br /&gt;    fi&lt;br /&gt;&lt;br /&gt;  pid=`erlstart-eval 'os:getpid ()' 2&gt;/dev/null`&lt;br /&gt;&lt;br /&gt;  test "$pid" -gt 0 2&gt;/dev/null &amp;amp;&amp;amp; {&lt;br /&gt;    printf '%s' $pid &gt; "$shutdown_file"&lt;br /&gt;    chown $user:$user "$shutdown_file"&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  erlstart-eval 'init:stop ()' &gt;/dev/null 2&gt;/dev/null&lt;br /&gt;&lt;br /&gt;  eval_with_main_node "$full_name"                                    \&lt;br /&gt;      "Wait = fun (_, 0) -&gt;&lt;br /&gt;                    failed;&lt;br /&gt;                  (Cont, Max) -&gt;&lt;br /&gt;                    case net_adm:ping (MainNode) of&lt;br /&gt;                      pong -&gt;&lt;br /&gt;                        timer:sleep (100),&lt;br /&gt;                        Cont (Cont, Max - 1);&lt;br /&gt;                      pang -&gt;&lt;br /&gt;                        ok&lt;br /&gt;                    end&lt;br /&gt;              end,&lt;br /&gt;       DontTellMe = 100,&lt;br /&gt;       ok = Wait (Wait, DontTellMe)" || {&lt;br /&gt;    echo "" 1&gt;&amp;amp;2&lt;br /&gt;    echo "$id: node still responsive after 100 seconds" 1&gt;&amp;amp;2&lt;br /&gt;    exit 1&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  rm -f "$node_name_file"&lt;br /&gt;&lt;br /&gt;  echo "done."&lt;br /&gt;  ;;&lt;br /&gt;&lt;br /&gt;status)&lt;br /&gt;  if test -f "$node_name_file" &amp;amp;&amp;amp;                                     \&lt;br /&gt;     test true = "`erlstart-eval 'true' 2&gt;/dev/null`" 2&gt;/dev/null&lt;br /&gt;    then&lt;br /&gt;      echo "Erlang is running"&lt;br /&gt;    else&lt;br /&gt;      echo "Erlang is not running"&lt;br /&gt;    fi&lt;br /&gt;&lt;br /&gt;  ;;&lt;br /&gt;&lt;br /&gt;*)&lt;br /&gt;  echo "$id: unknown command $1" 1&gt;&amp;amp;2&lt;br /&gt;  exit 1&lt;br /&gt;esac&lt;br /&gt;&lt;br /&gt;exit 0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Hopefully you found that inspirational.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-6622961196913363729?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/6622961196913363729/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=6622961196913363729' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6622961196913363729'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6622961196913363729'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2010/01/minor-erlang-interface-tricks.html' title='Minor Erlang Interface Tricks'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-3617401259151213935</id><published>2009-12-23T10:57:00.000-08:00</published><updated>2009-12-23T15:12:51.317-08:00</updated><title type='text'>erlrc and rpm</title><content type='html'>Two of the Dukes now work at &lt;a href="http://www.openx.org/"&gt;OpenX&lt;/a&gt;, which is starting to dip its toe into the Erlang waters.  They use &lt;a href="http://www.centos.org/"&gt;CentOS &lt;/a&gt;so they agreed to fund porting &lt;a href="http://code.google.com/p/fwtemplates/"&gt;framewerk&lt;/a&gt; and &lt;a href="http://code.google.com/p/erlrc/"&gt;erlrc&lt;/a&gt; to rpm; previously I'd only used them with deb.&lt;br /&gt;&lt;br /&gt;A bit of background: erlrc is a set of Erlang modules and shell scripts that are designed to be integrated into packaging hooks so that installation, upgrade, or removal of a package causes corresponding hot-code activity to happen inside registered Erlang VMs on the box.  Since erlrc was designed to easily integrate with multiple package managers, getting it to work with rpm was mostly about me understanding rpm's package hooks model.  The result is an experience like this,&lt;br /&gt;&lt;pre class="brush:bash"&gt;&lt;br /&gt;% sudo yum -q -y install lyet&lt;br /&gt;erlrc-start: Starting 'lyet': (erlang) started&lt;br /&gt;% sudo yum -q -y remove lyet&lt;br /&gt;erlrc-stop: Stopping 'lyet': (erlang) unloaded&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;i.e., installing an rpm causes a running Erlang VM on the box to hot-code load the new modules and start the associated application, and removing the rpm causes the associated application to be stopped and the corresponding modules to be hot-code unloaded.&lt;br /&gt;&lt;br /&gt;If you use &lt;a href="http://code.google.com/p/fwtemplates/wiki/FwTemplateErlangWalkthrough"&gt;fw-template-erlang&lt;/a&gt; than the appropriate packaging hooks are added for you automatically, both for deb and now rpm.  However even manual creation of rpm spec files is pretty easy:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;erlrc-stop is called in %preun if the installation count indicates removal&lt;/li&gt;&lt;li&gt;erlrc-upgrade is called in %preun if the installation count indicates upgrade&lt;/li&gt;&lt;li&gt;erlrc-start is called in %posttrans&lt;/li&gt;&lt;/ul&gt;Also, the erlrc shell scripts want to know the previously installed version, so I call &lt;span style="font-family:courier new;"&gt;rpm -q&lt;/span&gt; in a %pretrans hook and save the result.  Longer term, erlrc should probably ask the Erlang VM it is talking to what version is running to eliminate the need for this argument (I was a bit surprised that rpm doesn't provide this argument to the package hook like debian does; it seems very useful for creating version-specific upgrade fixes).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-3617401259151213935?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/3617401259151213935/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=3617401259151213935' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/3617401259151213935'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/3617401259151213935'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/12/erlrc-and-rpm.html' title='erlrc and rpm'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-6455840009796564696</id><published>2009-10-06T16:03:00.001-07:00</published><updated>2009-10-06T16:09:25.309-07:00</updated><title type='text'>Linear Programming with Erlang</title><content type='html'>So you have to solve a &lt;a href="http://en.wikipedia.org/wiki/Linear_programming"&gt;linear program&lt;/a&gt;, so naturally the first language you think of is Erlang.  Actually, it's not a natural first choice for most people, but if you are solving a linear program as part of an automatic control strategy for an internet facing application, the choice is better motivated.&lt;br /&gt;&lt;br /&gt;Since I faced this situation recently I wrote a binding for &lt;a href="http://www.gnu.org/software/glpk/"&gt;GLPK&lt;/a&gt; for Erlang.  Writing port drivers is a drag so I actually wrote a program to generate the C and Erlang for me.  Perhaps with the new FFI these sort of games will not be necessary, but I was happy with the approach because I anticipate experimenting with several linear programming packages, which should be much easier to accommodate.&lt;br /&gt;&lt;br /&gt;Available at &lt;a href="http://code.google.com/p/glpkerl/"&gt;Google code&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-6455840009796564696?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/6455840009796564696/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=6455840009796564696' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6455840009796564696'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6455840009796564696'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/10/linear-programming-with-erlang.html' title='Linear Programming with Erlang'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-3140532750232906981</id><published>2009-09-10T10:52:00.000-07:00</published><updated>2009-09-10T12:46:09.450-07:00</updated><title type='text'>Removing warnings from ct_expand</title><content type='html'>I use &lt;a href="http://forum.trapexit.org/viewtopic.php?p=20260"&gt;ct_expand&lt;/a&gt; alot (can you tell?).  The released version outputs warnings about unused variables, which are harmless, &lt;span style="font-style:italic;"&gt;except&lt;/span&gt; ... that it conditioned me to ignore unused variable warnings from the compiler.  Then the other day I had a real bug which the compiler was warning me about (via an unused variable) that took me quite a bit of time to find.  Lesson learned: make sure that a correct compile is as quiet as possible, so problems stand out.&lt;br /&gt;&lt;br /&gt;Ulf gave me some pointers on how to prevent ct_expand from emitting these warnings.  Basically, ensure that the temporary variables created by ct_expand are prefixed with an underbar.  Here's a patch:&lt;br /&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;--- src/ct_expand.erl   5 Jun 2009 21:07:32 -0000       1.1&lt;br /&gt;+++ src/ct_expand.erl   10 Sep 2009 04:12:07 -0000&lt;br /&gt;@@ -139,6 +139,9 @@&lt;br /&gt;                  {Fname, Arity} = erl_syntax_lib:analyze_function(Form),&lt;br /&gt;                  VarNames = erl_syntax_lib:new_variable_names(&lt;br /&gt;                               Arity,&lt;br /&gt;+                               fun (N) -&amp;gt;&lt;br /&gt;+                                 list_to_atom ("_V" ++ integer_to_list (N))&lt;br /&gt;+                               end,&lt;br /&gt;                               erl_syntax_lib:variables(Form)),&lt;br /&gt;                  {Form, [{function, Fname},&lt;br /&gt;                          {arity, Arity},&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-3140532750232906981?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/3140532750232906981/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=3140532750232906981' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/3140532750232906981'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/3140532750232906981'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/09/removing-warnings-from-ctexpand.html' title='Removing warnings from ct_expand'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-3803820698092020417</id><published>2009-09-08T11:33:00.000-07:00</published><updated>2009-09-08T11:38:32.712-07:00</updated><title type='text'>Application Variables during Upgrade</title><content type='html'>A quick note related to my &lt;a href="http://dukesoferl.blogspot.com/2009/08/dynamically-loading-webmachine.html"&gt;previous post&lt;/a&gt;: it turns out the application controller gives "old values" for application variables during the code upgrade process.  Therefore the call to&lt;br /&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;element (2, application:get_key (drurly, modules))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;returns the old list of modules, i.e., is missing new modules.  &lt;a href="http://forum.trapexit.org/viewtopic.php?p=20260"&gt;ct_expand&lt;/a&gt; to the rescue!  I get the list of modules at compile time from the application specification via&lt;br /&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;  Modules = &lt;br /&gt;    ct_expand:term (&lt;br /&gt;      begin&lt;br /&gt;        { ok, [ { application, drurly, PropList } ] } = &lt;br /&gt;          file:consult ("drurly.app"),&lt;br /&gt;        case proplists:get_value (modules, PropList) of&lt;br /&gt;          Y when is_list (Y) -&amp;gt; Y&lt;br /&gt;        end&lt;br /&gt;      end&lt;br /&gt;    ),&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;For this to work, you must ensure the application specification is generated prior to compiling the module with this function in it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-3803820698092020417?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/3803820698092020417/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=3803820698092020417' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/3803820698092020417'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/3803820698092020417'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/09/application-variables-during-upgrade.html' title='Application Variables during Upgrade'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-121861251171528740</id><published>2009-08-26T21:19:00.001-07:00</published><updated>2009-08-26T21:57:48.551-07:00</updated><title type='text'>Dynamically Loading Webmachine Resources</title><content type='html'>I've been using &lt;a href="http://bitbucket.org/justin/webmachine/wiki/Home"&gt;webmachine&lt;/a&gt; lately, which is fabulous for REST server development.  It has a modular concept of &lt;a href="http://bitbucket.org/justin/webmachine/wiki/WebmachineResources"&gt;resources&lt;/a&gt; and the design could be summarized as a "RESTlet container".  Because Erlang has hot code loading I was interested in doing this dynamically; in other words, I want to write a module which provides a webmachine resource, hot code load it into the system, and have webmachine start dispatching requests to it.&lt;br /&gt;&lt;br /&gt;The good news is, you can change the dispatch list at any time, via&lt;br /&gt;&lt;pre class="brush:erlang"&gt;application:set_env (webmachine, dispatch_list, NewDispatchList).&lt;br /&gt;&lt;/pre&gt;This does not upset webmachine and it will see the change immediately.  So for a single application leveraging webmachine, this seemed as simple as having a code_change handler execute something like&lt;br /&gt;&lt;pre class="brush:erlang"&gt;application:set_env&lt;br /&gt;  (webmachine,&lt;br /&gt;   dispatch_list,&lt;br /&gt;   lists:foldl (fun (Mod, Acc) -&amp;gt;&lt;br /&gt;                  case catch Mod:dispatch_rules () of&lt;br /&gt;                    { 'EXIT', _ } -&amp;gt; Acc;&lt;br /&gt;                    X -&amp;gt; X ++ Acc &lt;br /&gt;                  end&lt;br /&gt;                end,&lt;br /&gt;                [],&lt;br /&gt;                element (2, application:get_key (drurly, modules)))).&lt;br /&gt;&lt;/pre&gt;(drurly is the name of the application in this case).  Now my modules export a function like&lt;br /&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;dispatch_rules () -&gt;&lt;br /&gt;  [ { [ "clip" ], ?MODULE, [] },&lt;br /&gt;    { [ "clip", id ], ?MODULE, [] }&lt;br /&gt;  ].&lt;br /&gt;&lt;/pre&gt;if they are webmachine resources.&lt;br /&gt;&lt;br /&gt;The problem is that order is important: the &lt;span style="font-family:courier new;"&gt;webmachine_dispatch_list&lt;/span&gt; is consulted in order, and the first match is executed.  To solve this I created a sort function which orders the dispatch rules by "specificity"&lt;br /&gt;&lt;pre class="brush:erlang"&gt;path_spec_priority ('*') -&gt; 3;&lt;br /&gt;path_spec_priority (X) when is_atom (X) -&gt; 2;&lt;br /&gt;path_spec_priority (X) when is_list (X) -&gt; 1.&lt;br /&gt;&lt;br /&gt;dispatch_specificity ({ PathSpecA, _, _ },&lt;br /&gt;                      { PathSpecB, _, _ }) -&gt;&lt;br /&gt;  case erlang:length (PathSpecA) - erlang:length (PathSpecB) of&lt;br /&gt;    X when X &gt; 0 -&gt;&lt;br /&gt;      true;&lt;br /&gt;    X when X &lt; 0 -&gt;&lt;br /&gt;      false;&lt;br /&gt;    _ -&gt;&lt;br /&gt;      PrioPathSpecA = [ path_spec_priority (X) || X &lt;- PathSpecA ],&lt;br /&gt;      PrioPathSpecB = [ path_spec_priority (X) || X &lt;- PathSpecB ],&lt;br /&gt;&lt;br /&gt;      case PrioPathSpecA =&lt; PrioPathSpecB of&lt;br /&gt;        false -&gt;&lt;br /&gt;          false;&lt;br /&gt;        true -&gt;&lt;br /&gt;          FullPathSpecA = [ { path_spec_priority (X), X } || X &lt;- PathSpecA ],&lt;br /&gt;          FullPathSpecB = [ { path_spec_priority (X), X } || X &lt;- PathSpecB ],&lt;br /&gt;&lt;br /&gt;          FullPathSpecA =&lt; FullPathSpecB&lt;br /&gt;      end&lt;br /&gt;  end.&lt;br /&gt;&lt;/pre&gt;Basically:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Longer dispatch paths come first.&lt;/li&gt;&lt;li&gt;If two dispatch paths have equal length, the more specific one comes first, where specificity is defined by examining the elements left-to-right, with&lt;ul&gt;&lt;li&gt;string literals are most specific&lt;/li&gt;&lt;li&gt;atoms except '*' are the next most specific&lt;/li&gt;&lt;li&gt;'*' is the least specific&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;If two dispatch paths have equal length and specificity, sort by Erlang term order (effectively, break ties arbitrarily)&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;The code change handler now becomes:&lt;br /&gt;&lt;pre class="brush:erlang"&gt;application:set_env&lt;br /&gt;  (webmachine,&lt;br /&gt;   dispatch_list,&lt;br /&gt;   lists:sort&lt;br /&gt;     (fun dispatch_specificity/2,&lt;br /&gt;      lists:foldl (fun (Mod, Acc) -&amp;gt;&lt;br /&gt;                     case catch Mod:dispatch_rules () of&lt;br /&gt;                       { 'EXIT', _ } -&amp;gt; Acc;&lt;br /&gt;                       X -&amp;gt; X ++ Acc &lt;br /&gt;                     end&lt;br /&gt;                   end,&lt;br /&gt;                   [],&lt;br /&gt;                   element (2, application:get_key (drurly, modules))))).&lt;br /&gt;&lt;/pre&gt;This has proven sufficient for a single application running webmachine.&lt;br /&gt;&lt;br /&gt;For multiple applications that want to run under the same webmachine, I suspect the right way to go is to have a gen_server which&lt;br /&gt;&lt;ul&gt;&lt;li&gt; contains webmachine dispatch configuration "fragments" per key, where the key is intended to be the application name;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;accepts commands to replace or delete a particular fragment by key;&lt;/li&gt;&lt;li&gt;for any replace or delete, rebuilds the complete dispatch list by concatenating all fragments together and sorting by specificity, and then updates the webmachine application env var.&lt;/li&gt;&lt;/ul&gt;For multiple applications that want to run under different webmachines, well unfortunately webmachine uses some global application settings under the fixed atom webmachine and thus currently, like the highlander, there can be only one.  (In fact, you can only listen on one ip/port combination with webmachine right now.  I might have to patch it to accept multiple ip/port combinations to listen to, since a standard trick of mine is to have &lt;a href="http://nginx.net/"&gt;nginx&lt;/a&gt; handle both regular and ssl connections and connect to a different back-end port to indicate whether or not the connection is secure.)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-121861251171528740?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/121861251171528740/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=121861251171528740' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/121861251171528740'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/121861251171528740'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/08/dynamically-loading-webmachine.html' title='Dynamically Loading Webmachine Resources'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-2878174661258709261</id><published>2009-08-22T13:21:00.000-07:00</published><updated>2009-08-22T13:49:58.464-07:00</updated><title type='text'>Metaprogramming with ct_expand</title><content type='html'>Yesterday I &lt;a href="http://dukesoferl.blogspot.com/2009/08/erlang-terms-in-cookies.html"&gt;posted&lt;/a&gt; about putting arbitrary Erlang terms into HTTP cookies.  I suggested that constructing the base64 codec at compile time was an excellent application for &lt;a href="http://forum.trapexit.org/viewtopic.php?p=20260"&gt;ct_expand&lt;/a&gt;, but I didn't provide any details.  Therefore, here is a follow-up.&lt;br /&gt;&lt;br /&gt;ct_expand provides a simple interface to Erlang metaprogramming: evaluating an arbitrary Erlang term at compile time and substituting the results into the source code during compilation.  This is especially useful for initializing a data structure at compile time, and in particular, it can be used to construct the forward and inverse maps for the base64 codec.  We will specify our codec by providing a list of 64 unique characters, and ct_expand will do the rest.  The following code is functionally identical to the termcookie module &lt;a href="http://dukesoferl.blogspot.com/2009/08/erlang-terms-in-cookies.html"&gt;presented previously&lt;/a&gt;.&lt;br /&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;-module (termcookie2).&lt;br /&gt;-compile ({ parse_transform, ct_expand }).&lt;br /&gt;-export ([ decode/2,&lt;br /&gt;          encode/2 ]).&lt;br /&gt;&lt;br /&gt;-define (CODEC, "abcdefghijklmnopqrstuvwxyz"&lt;br /&gt;               "ABCDEFGHIJKLMNOPQRSTUVWXYZ"&lt;br /&gt;               "0123456789.,").&lt;br /&gt;&lt;br /&gt;%&lt;br /&gt;% Public&lt;br /&gt;%&lt;br /&gt;&lt;br /&gt;decode (Encoded, Secret) when is_binary (Encoded) -&amp;gt;&lt;br /&gt; &amp;lt;&amp;lt;Signature:28/binary, Payload/binary&amp;gt;&amp;gt; = Encoded,&lt;br /&gt; Signature = to_base64 (crypto:sha ([ Payload, Secret ])),&lt;br /&gt; erlang:binary_to_term (from_base64 (Payload)).&lt;br /&gt;&lt;br /&gt;encode (Term, Secret) -&amp;gt;&lt;br /&gt; Payload =&lt;br /&gt;   to_base64&lt;br /&gt;     (erlang:term_to_binary (Term,&lt;br /&gt;                             [ compressed,&lt;br /&gt;                               { minor_version, 1 } ])),&lt;br /&gt; Signature = to_base64 (crypto:sha ([ Payload, Secret ])),&lt;br /&gt; &amp;lt;&amp;lt;Signature/binary, Payload/binary&amp;gt;&amp;gt;.&lt;br /&gt;&lt;br /&gt;%&lt;br /&gt;% Private&lt;br /&gt;%&lt;br /&gt;&lt;br /&gt;to_base64 (Bin) when (8 * byte_size (Bin)) rem 6 =:= 0 -&amp;gt;&lt;br /&gt; to_base64_padded (Bin);&lt;br /&gt;to_base64 (Bin) when (8 * byte_size (Bin)) rem 6 =:= 2 -&amp;gt;&lt;br /&gt; to_base64_padded (&amp;lt;&amp;lt;Bin/binary, 0:16&amp;gt;&amp;gt;);&lt;br /&gt;to_base64 (Bin) when (8 * byte_size (Bin)) rem 6 =:= 4 -&amp;gt;&lt;br /&gt; to_base64_padded (&amp;lt;&amp;lt;Bin/binary, 0:8&amp;gt;&amp;gt;).&lt;br /&gt;&lt;br /&gt;to_base64_padded (Bin) -&amp;gt;&lt;br /&gt; &amp;lt;&amp;lt; &amp;lt;&amp;lt;(element (N + 1,&lt;br /&gt;                ct_expand:term (&lt;br /&gt;                  begin&lt;br /&gt;                    64 = length (?CODEC),&lt;br /&gt;                    64 = length (lists:usort (?CODEC)),&lt;br /&gt;                    list_to_tuple (?CODEC)&lt;br /&gt;                  end&lt;br /&gt;                )&lt;br /&gt;               )&lt;br /&gt;      ):8&amp;gt;&amp;gt;&lt;br /&gt;    || &amp;lt;&amp;lt;N:6&amp;gt;&amp;gt; &amp;lt;= Bin &amp;gt;&amp;gt;.&lt;br /&gt;&lt;br /&gt;from_base64 (Bin) -&amp;gt;&lt;br /&gt; &amp;lt;&amp;lt; &amp;lt;&amp;lt;(element&lt;br /&gt;         (N + 1,&lt;br /&gt;          ct_expand:term&lt;br /&gt;            (element&lt;br /&gt;              (2,&lt;br /&gt;               lists:foldl&lt;br /&gt;                 (fun (X, { K, T }) -&amp;gt;&lt;br /&gt;                    { K + 1, setelement (X + 1, T, K) }&lt;br /&gt;                  end,&lt;br /&gt;                  { 0, erlang:make_tuple (256, -1) },&lt;br /&gt;                  ?CODEC)&lt;br /&gt;              )&lt;br /&gt;            )&lt;br /&gt;         )&lt;br /&gt;      ):6&amp;gt;&amp;gt;&lt;br /&gt;     || &amp;lt;&amp;lt;N:8&amp;gt;&amp;gt; &amp;lt;= Bin &amp;gt;&amp;gt;.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Some notes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Lines 6-8 contain the specification of the codec.&lt;/li&gt;&lt;li&gt;Lines 43-44 are compile-time assertions on the codec specification, namely that it consists of 64 unique characters.  If you modify the specification to violate these assertions, the module will not compile, although the resulting error message will be nearly unintelligible (try it!).&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Line 45 constructs the forward mapping from the specification; the result is a (constant) tuple which is consulted at run time.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Lines 56-65 construct the inverse mapping from the specification; the result is a (constant) tuple which is consulted at run time.&lt;/li&gt;&lt;/ul&gt;Note you cannot use any functions from the current module inside the &lt;span style="font-family: courier new;"&gt;ct_expand:term/1&lt;/span&gt; argument, because the current module has not been compiled yet!  If you need to do something really complicated and don't like an unwieldy inline expression you can place helper code in a separate module which is compiled first.&lt;br /&gt;&lt;br /&gt;The resulting software is easier to maintain than the original version, because if the codec needs to be changed, only the specification is modified.  For instance we can replace lines 5-8 with&lt;br /&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;-define (CHARS, "abcdefghijklmnopqrstuvwxyz"&lt;br /&gt;               "ABCDEFGHIJKLMNOPQRSTUVWXYZ"&lt;br /&gt;               "0123456789.,").&lt;br /&gt;-define (CODEC,&lt;br /&gt;        ct_expand:term (&lt;br /&gt;          fun () -&amp;gt;&lt;br /&gt;            random:seed (1, 2, 3),&lt;br /&gt;            [ X ||&lt;br /&gt;              { _, X } &amp;lt;-&lt;br /&gt;                lists:sort (&lt;br /&gt;                  [ { random:uniform (), Y }&lt;br /&gt;                    || Y &amp;lt;- ?CHARS&lt;br /&gt;                  ]&lt;br /&gt;                )&lt;br /&gt;            ]&lt;br /&gt;          end ()&lt;br /&gt;        )).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;which results in a proper codec utilizing a permutation of the original codec.  Note the call to random:seed/3 is happening at compile time, and is setting the random seed of the compilation process.  Therefore this is a stable codec definition.  (Unfortunately, it doesn't really increase the opacity of the encoding scheme; the bits in an encoded Erlang term are highly degenerate so any interested party would be able to deduce the permutation given enough cookies).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-2878174661258709261?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/2878174661258709261/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=2878174661258709261' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/2878174661258709261'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/2878174661258709261'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/08/metaprogramming-with-ctexpand.html' title='Metaprogramming with ct_expand'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-1902957591996389575</id><published>2009-08-21T16:33:00.000-07:00</published><updated>2009-08-21T23:31:17.627-07:00</updated><title type='text'>Erlang Terms in Cookies</title><content type='html'>Erlang is an increasingly popular choice for web development.  Such projects tend to heavily leverage HTTP cookies, and because Erlang defines an external format for any Erlang term, it turns out to be very easy to store an arbitrary term with a cookie (within cookie size limitations), which can be a useful trick.&lt;br /&gt;&lt;br /&gt;The technique outlined here generates a signed but not encrypted cookie.  That means it's fairly simple for anyone possessing one of your cookies to determine the contents, but difficult for them to forge a novel cookie.  The latter is typically important for the application, but has additional importance here because we will be calling &lt;span style="font-family:courier new;"&gt;erlang:binary_to_term/1&lt;/span&gt; on the cookie value and passing arbitrary data to &lt;span style="font-family:courier new;"&gt;erlang:binary_to_term/1&lt;/span&gt; is a bad idea (for example, this could cause an extremely large memory allocation).&lt;br /&gt;&lt;br /&gt;Here's the code:&lt;br /&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;-module (termcookie).&lt;br /&gt;-export ([ decode/2,&lt;br /&gt;      encode/2 ]).&lt;br /&gt;&lt;br /&gt;%&lt;br /&gt;% Public&lt;br /&gt;%&lt;br /&gt;&lt;br /&gt;decode (Encoded, Secret) when is_binary (Encoded) -&amp;gt;&lt;br /&gt;&amp;lt;&amp;lt;Signature:28/binary, Payload/binary&amp;gt;&amp;gt; = Encoded,&lt;br /&gt;Signature = to_base64 (crypto:sha ([ Payload, Secret ])),&lt;br /&gt;erlang:binary_to_term (from_base64 (Payload)).&lt;br /&gt;&lt;br /&gt;encode (Term, Secret) -&amp;gt;&lt;br /&gt;Payload =&lt;br /&gt;to_base64&lt;br /&gt; (erlang:term_to_binary (Term,&lt;br /&gt;                         [ compressed,&lt;br /&gt;                           { minor_version, 1 } ])),&lt;br /&gt;Signature = to_base64 (crypto:sha ([ Payload, Secret ])),&lt;br /&gt;&amp;lt;&amp;lt;Signature/binary, Payload/binary&amp;gt;&amp;gt;.&lt;br /&gt;&lt;br /&gt;%&lt;br /&gt;% Private&lt;br /&gt;%&lt;br /&gt;&lt;br /&gt;to_base64 (Bin) when (8 * byte_size (Bin)) rem 6 =:= 0 -&amp;gt;&lt;br /&gt;to_base64_padded (Bin);&lt;br /&gt;to_base64 (Bin) when (8 * byte_size (Bin)) rem 6 =:= 2 -&amp;gt;&lt;br /&gt;to_base64_padded (&amp;lt;&amp;lt;Bin/binary, 0:16&amp;gt;&amp;gt;);&lt;br /&gt;to_base64 (Bin) when (8 * byte_size (Bin)) rem 6 =:= 4 -&amp;gt;&lt;br /&gt;to_base64_padded (&amp;lt;&amp;lt;Bin/binary, 0:8&amp;gt;&amp;gt;).&lt;br /&gt;&lt;br /&gt;to_base64_padded (Bin) -&amp;gt;&lt;br /&gt;&amp;lt;&amp;lt; &amp;lt;&amp;lt;(to_base64_char (N)):8&amp;gt;&amp;gt; || &amp;lt;&amp;lt;N:6&amp;gt;&amp;gt; &amp;lt;= Bin &amp;gt;&amp;gt;.&lt;br /&gt;&lt;br /&gt;to_base64_char (N) when N &amp;gt;= 0, N =&amp;lt; 25 -&amp;gt; $a + N;&lt;br /&gt;to_base64_char (N) when N &amp;gt;= 26, N =&amp;lt; 51 -&amp;gt; $A + (N - 26);&lt;br /&gt;to_base64_char (N) when N &amp;gt;= 52, N =&amp;lt; 61 -&amp;gt; $0 + (N - 52);&lt;br /&gt;to_base64_char (62) -&amp;gt; $.;&lt;br /&gt;to_base64_char (63) -&amp;gt; $,.&lt;br /&gt;&lt;br /&gt;from_base64 (Bin) -&amp;gt;&lt;br /&gt;&amp;lt;&amp;lt; &amp;lt;&amp;lt;(from_base64_char (N)):6&amp;gt;&amp;gt; || &amp;lt;&amp;lt;N:8&amp;gt;&amp;gt; &amp;lt;= Bin &amp;gt;&amp;gt;.&lt;br /&gt;&lt;br /&gt;from_base64_char (N) when N &amp;gt;= $a, N =&amp;lt; $z -&amp;gt; N - $a;&lt;br /&gt;from_base64_char (N) when N &amp;gt;= $A, N =&amp;lt; $Z -&amp;gt; 26 + (N - $A);&lt;br /&gt;from_base64_char (N) when N &amp;gt;= $0, N =&amp;lt; $9 -&amp;gt; 52 + (N - $0);&lt;br /&gt;from_base64_char ($.) -&amp;gt; 62;&lt;br /&gt;from_base64_char ($,) -&amp;gt; 63.&lt;br /&gt;&lt;/pre&gt;We are taking advantage of the fact that &lt;span style="font-family:courier new;"&gt;erlang:binary_to_term/1&lt;/span&gt; will ignore extra bytes at the end, which allows us to mindlessly pad for base 64 encoding.&lt;br /&gt;&lt;br /&gt;If you really like to squeeze the last few drops of efficiency out of code, you can change those &lt;span style="font-family:courier new;"&gt;to_base64_char/1&lt;/span&gt; and &lt;span style="font-family:courier new;"&gt;from_base64_char/1&lt;/span&gt; functions into tuple lookups.  If you are extra cool you can use Ulf Wiger's &lt;a href="http://forum.trapexit.org/viewtopic.php?p=20260"&gt;ct_expand&lt;/a&gt; parse transform to construct the tuples at compile time from a specified character list.&lt;br /&gt;&lt;br /&gt;This code will throw an exception if anything is amiss with the input, including a signature fail.&lt;br /&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;% erl&lt;br /&gt;Erlang (BEAM) emulator version 5.6.5 [source] [async-threads:0] [kernel-poll:false]&lt;br /&gt;&lt;br /&gt;Eshell V5.6.5  (abort with ^G)&lt;br /&gt;1&gt; crypto:start ().&lt;br /&gt;ok&lt;br /&gt;2&gt; termcookie:encode ({ "omg", erlang, rulz }, "wazzup").&lt;br /&gt;&lt;&lt;"nQCwmuMgeK3bTPzBqKDSmSylIciaG2GdAWadB21NzaagzxjSyw5NzaaeCNvSEGaa"&gt;&gt;&lt;br /&gt;3&gt; termcookie:decode (termcookie:encode ({ "omg", erlang, rulz }, "wazzup"), "wazzup").&lt;br /&gt;{"omg",erlang,rulz}&lt;br /&gt;4&gt; termcookie:decode (termcookie:encode ({ "omg", erlang, rulz }, "huh"), "wazzup").&lt;br /&gt;** exception error: no match of right hand side value &lt;&lt;"nQCwmuMgeK3bTPzBqKDSmSylIcia"&gt;&gt;&lt;br /&gt; in function  termcookie:decode/2&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-1902957591996389575?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/1902957591996389575/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=1902957591996389575' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/1902957591996389575'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/1902957591996389575'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/08/erlang-terms-in-cookies.html' title='Erlang Terms in Cookies'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-6288780980970696783</id><published>2009-08-08T23:00:00.001-07:00</published><updated>2009-08-09T12:33:24.243-07:00</updated><title type='text'>Implementing a DSL in Erlang</title><content type='html'>If you are willing to abide by Erlang syntax, then you can leverage erl_parse, erl_scan, and erl_eval to quickly whip up a domain specific language (DSL).  You can manipulate the semantics via a combination of transformations on the parse tree (with the help of erl_syntax) and interception of function calls (which comes with erl_eval).&lt;br /&gt;&lt;br /&gt;For instance, suppose we want a domain specific language which is just like Erlang, except that it has destructive assignment.  This can be done in three steps: 1) parse the input using the Erlang parser, 2) transform the parse tree so that match expressions are rewritten as a special local function call, and 3) eval the result, intercept the special local function call and implement the destructive assignment by recursive eval.&lt;br /&gt;&lt;br /&gt;Here's the code:&lt;br /&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;-module (dsl).&lt;br /&gt;-export ([ compile/1,&lt;br /&gt;           run/2 ]).&lt;br /&gt;&lt;br /&gt;%-========================================================-&lt;br /&gt;%-                   Public                               -&lt;br /&gt;%-========================================================-&lt;br /&gt;&lt;br /&gt;compile (String) -&gt;&lt;br /&gt;  { ok, Scanned, _ } = &lt;br /&gt;    erl_scan:string (maybe_append_dot (String)),&lt;br /&gt;  { ok, Parsed } = erl_parse:parse_exprs (Scanned),&lt;br /&gt;  { dsl, transform (Parsed) }.&lt;br /&gt;&lt;br /&gt;run ({ dsl, Parsed }, Bindings) -&gt;&lt;br /&gt;  eval (Parsed, Bindings).&lt;br /&gt;&lt;br /&gt;%-========================================================-&lt;br /&gt;%-               eval callbacks                           -&lt;br /&gt;%-========================================================-&lt;br /&gt;&lt;br /&gt;local ('_assign', [ Pattern, Expr ], Bindings) -&gt;&lt;br /&gt;  { value, Rhs, NewBindings } = &lt;br /&gt;    eval ([ Expr ], Bindings),&lt;br /&gt;  MatchExpr = &lt;br /&gt;    erl_syntax:match_expr (Pattern, &lt;br /&gt;                           erl_syntax:abstract (Rhs)),&lt;br /&gt;  { value, _Lhs, MatchBindings } = &lt;br /&gt;    eval ([ erl_syntax:revert (MatchExpr) ],&lt;br /&gt;            erl_eval:new_bindings ()),&lt;br /&gt;  { value, &lt;br /&gt;    Rhs, &lt;br /&gt;    destructive_merge (NewBindings, MatchBindings) }.&lt;br /&gt;&lt;br /&gt;%-========================================================-&lt;br /&gt;%-                  Private                               -&lt;br /&gt;%-========================================================-&lt;br /&gt;&lt;br /&gt;destructive_merge (Bindings, MatchBindings) -&gt;&lt;br /&gt;  lists:foldl&lt;br /&gt;    (fun ({ Key, Val }, Acc) -&gt;&lt;br /&gt;       erl_eval:add_binding &lt;br /&gt;        (Key, &lt;br /&gt;         Val, &lt;br /&gt;         erl_eval:del_binding (Key, Acc))&lt;br /&gt;     end,&lt;br /&gt;     Bindings,&lt;br /&gt;     erl_eval:bindings (MatchBindings)).&lt;br /&gt;&lt;br /&gt;eval (Parsed, Bindings) -&gt;&lt;br /&gt;  erl_eval:exprs (Parsed, &lt;br /&gt;                  Bindings, &lt;br /&gt;                  { eval, fun local/3 }).&lt;br /&gt;&lt;br /&gt;maybe_append_dot (String) -&gt;&lt;br /&gt;  case lists:last (string:strip (String, &lt;br /&gt;                                 right)) of&lt;br /&gt;    $. -&gt; String;&lt;br /&gt;    _ -&gt; String ++ "."&lt;br /&gt;  end.&lt;br /&gt;&lt;br /&gt;postorder (F, Tree) -&gt;&lt;br /&gt;  F (case erl_syntax:subtrees (Tree) of&lt;br /&gt;       [] -&gt; &lt;br /&gt;         Tree;&lt;br /&gt;       List -&gt; &lt;br /&gt;         erl_syntax:update_tree &lt;br /&gt;           (Tree,&lt;br /&gt;            [[ postorder (F, Subtree)&lt;br /&gt;             || Subtree &lt;- Group ]&lt;br /&gt;            || Group &lt;- List ])&lt;br /&gt;     end).&lt;br /&gt;&lt;br /&gt;transform (Parsed) -&gt;&lt;br /&gt;  F = fun (Node) -&gt;&lt;br /&gt;        case erl_syntax:type (Node) of&lt;br /&gt;          % match_expr: rewrite as (destructive) assignment&lt;br /&gt;          match_expr -&gt;&lt;br /&gt;            erl_syntax:application &lt;br /&gt;              (none,&lt;br /&gt;               erl_syntax:atom ("_assign"),&lt;br /&gt;               [ erl_syntax:match_expr_pattern (Node),&lt;br /&gt;                 erl_syntax:match_expr_body (Node) ]);&lt;br /&gt;          _ -&gt;&lt;br /&gt;            Node&lt;br /&gt;        end&lt;br /&gt;      end,&lt;br /&gt;&lt;br /&gt;  [ erl_syntax:revert (postorder (F, X)) &lt;br /&gt;    || X &lt;- Parsed ].&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;dsl:compile/1 scans and parses the string in the standard way, but transforms the resulting parse tree rewriting match expressions as calls to a fictitious local function _assign/2.  This local function call is intercepted by specifying a local function handler to erl_eval:exprs/3.  At that point (lines 22-33), the sequence is: 1) evaluate the right hand side using the current bindings to reduce the right hand side to a constant, 2) evaluate a match of the left hand side to the constant expression given an empty set of bindings, and 3) merge the new bindings with the existing bindings, overwriting as necessary.  By proceeding in this fashion, we can still error out if we have a structural mismatch, or if an unbound variable is used on a right hand side.&lt;br /&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;% erl&lt;br /&gt;Erlang (BEAM) emulator version 5.6.5 [source] [async-threads:0] [kernel-poll:false]&lt;br /&gt;&lt;br /&gt;Eshell V5.6.5  (abort with ^G)&lt;br /&gt;1&gt; c (dsl).&lt;br /&gt;{ok,dsl}&lt;br /&gt;2&gt; dsl:run (dsl:compile ("A = 7, A = A + 3"), erl_eval:new_bindings ()).&lt;br /&gt;{value,10,[{'A',10}]}&lt;br /&gt;3&gt; dsl:run (dsl:compile ("B = 7, A = A + 3"), erl_eval:new_bindings ()).&lt;br /&gt;** exception error: variable 'A' is unbound&lt;br /&gt;4&gt; dsl:run (dsl:compile ("A = 7, { _, A } = A + 3"), erl_eval:new_bindings ()).&lt;br /&gt;** exception error: no match of right hand side value 10&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-6288780980970696783?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/6288780980970696783/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=6288780980970696783' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6288780980970696783'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6288780980970696783'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/08/implementing-dsl-in-erlang.html' title='Implementing a DSL in Erlang'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-3348110960442254490</id><published>2009-07-26T11:54:00.000-07:00</published><updated>2009-08-09T20:58:51.643-07:00</updated><title type='text'>Ets Ordered Set Select Efficiency</title><content type='html'>Ets (and &lt;a href="http://code.google.com/p/tcerl"&gt;tcerl&lt;/a&gt;) ordered_sets have the useful property that range queries on them can be resolved &lt;a href="http://erlang.org/documentation/doc-5.5.1/doc/efficiency_guide/tablesDatabases.html"&gt;more efficiently than full-table scan&lt;/a&gt;.  For instance, given the operation&lt;br /&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;ets:select (Tab, [ { { { foo, 1, '_' }, bar }, [], [ '$_' ] } ])&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;only the range of keys corresponding to 3-element tuples whose first two elements are 'foo' and 1 will be examined, and this is potentially far less than the entire set of keys.&lt;br /&gt;&lt;br /&gt;Recently I've been working on a project that requires a range of partially bound keys.  This would correspond to an operation such as&lt;br /&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;ets:select (Tab, [ { { { foo, '$1', '_' }, bar }, [ { '&gt;=', '$1', 1 }, { '=&lt;', '$1', 5 } ], [ '$_' ] } ]) &lt;/pre&gt;&lt;br /&gt;and so I made the modifications to tcerl to be able to &lt;a href="http://code.google.com/p/tcerl/source/browse/trunk/tcerl/src/tcbdbmsutil.erl"&gt;detect match conditions of this form&lt;/a&gt; and constrain the query range.  I started to wonder if I was reinventing the wheel and whether ets had sophisticated match specification analysis under the hood somewhere.  I &lt;a href="http://www.erlang.org/cgi-bin/ezmlm-cgi?4:mss:45570:200907:hgapefpolmhogcanghjo"&gt;asked the mailing list&lt;/a&gt; but answers were inconclusive so I decided to do some measurements.  Using the following module:&lt;br /&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;-module (etsselect).&lt;br /&gt;-compile (export_all).&lt;br /&gt;&lt;br /&gt;populate_ets (Tab, N) -&gt;&lt;br /&gt; ets:delete_all_objects (Tab),&lt;br /&gt; lists:foreach (fun (M) -&gt;&lt;br /&gt;                  ets:insert (Tab, { { foo, M div 10, M }, bar })&lt;br /&gt;                end,&lt;br /&gt;                lists:seq (1, N)).&lt;br /&gt;&lt;br /&gt;repeat_factor (Size) when Size &lt; 1000 -&gt; 100;&lt;br /&gt;repeat_factor (Size) when Size &lt; 100000 -&gt; 30;&lt;br /&gt;repeat_factor (_) -&gt; 10.&lt;br /&gt;&lt;br /&gt;select (MatchSpec, Tab, Repeat) when Repeat &gt; 0 -&gt;&lt;br /&gt; ets:select (Tab, MatchSpec),&lt;br /&gt; select (MatchSpec, Tab, Repeat - 1);&lt;br /&gt;select (_, _, _) -&gt;&lt;br /&gt; ok.&lt;br /&gt;&lt;br /&gt;select_time (MatchSpec) -&gt;&lt;br /&gt; Tab = ets:new (?MODULE, [ ordered_set ]),&lt;br /&gt; try&lt;br /&gt;   [ { N,&lt;br /&gt;       (fun () -&gt;&lt;br /&gt;          populate_ets (Tab, N),&lt;br /&gt;          { Time, _ } = timer:tc (?MODULE, select, [ MatchSpec, Tab, Repeat ]),&lt;br /&gt;          Time / Repeat&lt;br /&gt;        end) ()&lt;br /&gt;     }&lt;br /&gt;    || X &lt;- lists:seq (1, 9),&lt;br /&gt;       N &lt;- [ trunc (math:pow (5, X)) ],&lt;br /&gt;       Repeat &lt;- [ repeat_factor (N) ]&lt;br /&gt;   ]&lt;br /&gt; after   &lt;br /&gt;   ets:delete (Tab)      &lt;br /&gt; end.  &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I did some timing tests with different match specifications and table sizes.  The match specifications were:&lt;br /&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;[{ '_', [], [ '$_' ] }] % unconstrained&lt;br /&gt;[{{{ foo, 1, '_' }, bar }, [], [ '$_' ] }] % prefix bound&lt;br /&gt;[{{{ foo, '$1', '_' }, bar }, &lt;br /&gt;   [{ '&gt;=', '$1', 1 }, { '=&lt;', '$1', 1 }],&lt;br /&gt;   [ '$_' ]&lt;br /&gt; }] % prefix range bound&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The results (using R12B5) indicate that the prefix range bound condition is not being optimized by ets.&lt;br /&gt;&lt;pre face="courier new"&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_6GrYI7inWIk/SmytCEy1zjI/AAAAAAAAAGQ/GHT9r7h5j9I/s1600-h/etstiminggraph.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 228px;" src="http://4.bp.blogspot.com/_6GrYI7inWIk/SmytCEy1zjI/AAAAAAAAAGQ/GHT9r7h5j9I/s400/etstiminggraph.png" alt="" id="BLOGGER_PHOTO_ID_5362851507449744946" border="0" /&gt;&lt;/a&gt;&lt;/pre&gt;The tcerl timings indicate the prefix range bound condition is being optimized (they also indicate much worse constant factors then ets).&lt;br /&gt;&lt;pre face="courier new"&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_6GrYI7inWIk/SmyyHJPOSMI/AAAAAAAAAGY/DmDsWdkkEzY/s1600-h/tctiminggraph.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 228px;" src="http://1.bp.blogspot.com/_6GrYI7inWIk/SmyyHJPOSMI/AAAAAAAAAGY/DmDsWdkkEzY/s400/tctiminggraph.png" alt="" id="BLOGGER_PHOTO_ID_5362857092100016322" border="0" /&gt;&lt;/a&gt;&lt;/pre&gt;Note I haven't released these changes yet, but I will soon as 1.3.1h.  Versions of tcerl prior to 1.3.1h will do a full table scan in the prefix range bound case.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-3348110960442254490?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/3348110960442254490/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=3348110960442254490' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/3348110960442254490'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/3348110960442254490'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/07/ets-ordered-set-select-efficiency.html' title='Ets Ordered Set Select Efficiency'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_6GrYI7inWIk/SmytCEy1zjI/AAAAAAAAAGQ/GHT9r7h5j9I/s72-c/etstiminggraph.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-6808756941789255761</id><published>2009-07-01T13:59:00.000-07:00</published><updated>2009-07-01T14:45:31.939-07:00</updated><title type='text'>osmos</title><content type='html'>I just released the first version of &lt;a href="http://code.google.com/p/osmos/"&gt;osmos&lt;/a&gt;, a pure Erlang library that provides on-disk ordered set tables which allow thousands of updates per second with ACID properties.&lt;br /&gt;&lt;br /&gt;It achieves that update rate using a rather different structure from a traditional B-tree based table (like the ones used by most RDBMSs or provided by DBM libraries like BDB or tokyocabinet): an incremental merge sort with user-defined merge semantics.&lt;br /&gt;&lt;h4&gt;Motivation&lt;/h4&gt;Ordinarily, the rate of updates to an on-disk table is limited by the need to seek around and update an index in place. With a typical seek time on the order of 10 ms, this makes it challenging to scale past about 100 updates/s. Most strategies for going beyond that involve some kind of partitioning, either over multiple disks, multiple machines, or both.&lt;br /&gt;&lt;br /&gt;However, a few key observations&lt;sup&gt;&lt;a href="#footnote1"&gt;[1]&lt;/a&gt;&lt;/sup&gt; point to a different strategy:&lt;ol&gt;&lt;li&gt;The reason for updating an index on every write is the expectation that reads are much more frequent than writes, so that read efficiency is the dominating factor. But if writes are far more frequent than reads, you can use some kind of lazy updating to delay the work until absolutely necessary, and combine the work from multiple updates.&lt;/li&gt;&lt;li&gt;An extreme example of a write-dominated, lazily updated database is a full-text inverted index for a search engine. To build one, you might typically read in billions of term-document pairs, sort them by term using some form of &lt;a href="http://en.wikipedia.org/wiki/External_sorting"&gt;external merge sort&lt;/a&gt;, and then create a highly optimized index before ever handling a single query.&lt;/li&gt;&lt;li&gt;A merge sort can operate continuously, by injecting new records in sorted batches, and then merging the batches as necessary to maintain a set of sorted files with exponentially increasing sizes. And crucially, this kind of incremental merge sort process can allow relatively efficient searches of the data while it's operating, by binary-searching each sorted file. (An example of this is the incremental indexing provided by &lt;a href="http://lucene.apache.org/"&gt;Lucene&lt;/a&gt;.)&lt;/li&gt;&lt;/ol&gt;This gives you an ordered set table with a slight increase in the cost of a search (with N records, maybe an extra factor of log N). But the cost of a write is tiny, and mostly delayed: about log N future comparisons during merging, and log N future disk writes, but since all the disk writes are sequential, they will be buffered, and writing to the table requires no explicit seeking at all.&lt;sup&gt;&lt;a href="#footnote2"&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;br /&gt;&lt;h4&gt;User-defined merging&lt;/h4&gt;Things get even more interesting when you let the user control how records are merged. In the osmos model, there is at most one record for any given key; if two records with the same key are encountered during the merge sort, the user's merge function is called to merge the two records into a single record.&lt;br /&gt;&lt;br /&gt;The merge function can be any function&lt;pre&gt;&lt;br /&gt;  Merge(Key, EarlierValue, LaterValue)  -&gt; MergedValue&lt;br /&gt;&lt;/pre&gt;that is associative, i.e.,&lt;pre&gt;&lt;br /&gt;  Merge(K, Merge(K, V1, V2), V3) =:=&lt;br /&gt;  Merge(K, V1, Merge(K, V2, V3))&lt;br /&gt;&lt;/pre&gt;for any key K and any consecutive sequence of values V1, V2, V3.&lt;br /&gt;&lt;br /&gt;This allows a wide variety of semantics for writing to the table. For example:&lt;ul&gt;&lt;li&gt;If the merge function always returns the later value, then a write replaces any previous value, like an ordinary key-value store.&lt;/li&gt;&lt;li&gt;If the values are integers, and the merge function returns the sum of the two values, then writing to the table acts like transactionally incrementing a counter.&lt;/li&gt;&lt;/ul&gt;Similarly, you could use any associative function of two numbers; you could apply such a function to each element of a vector of numbers; or you could apply a different function to each element. For example, to keep a minimum, maximum, and average over some set of keys, you could use something like:&lt;pre&gt;&lt;br /&gt;  merge(_K, N1, N2)&lt;br /&gt;      when is_number(N1), is_number(N2) -&gt;&lt;br /&gt;    {min(N1, N2), max(N1, N2), N1 + N2, 2};&lt;br /&gt;  merge(_K, N1, {Min2, Max2, Sum2, Count2})&lt;br /&gt;      when is_number(N1) -&gt;&lt;br /&gt;    {min(N1, Min2), max(N1, Max2),&lt;br /&gt;     N1 + Sum2, 1 + Count2};&lt;br /&gt;  merge(_K, {Min1, Max1, Sum1, Count1}, N2)&lt;br /&gt;      when is_number(N2) -&gt;&lt;br /&gt;    {min(Min1, N2), max(Max1, N2),&lt;br /&gt;     Sum1 + N2, Count1 + 1};&lt;br /&gt;  merge(_K, {Min1, Max1, Sum1, Count1},&lt;br /&gt;            {Min2, Max2, Sum2, Count2}) -&gt;&lt;br /&gt;    {min(Min1, Min2), max(Max1, Max2),&lt;br /&gt;     Sum1 + Sum2, Count1 + Count2}.&lt;br /&gt;&lt;/pre&gt;This lets you write single numbers as values, but read back either &lt;tt&gt;{Min, Max, Sum, Count}&lt;/tt&gt; tuples (if more than one number has been written for a key) or single numbers (if that was the only value written). To do this with an ordinary key-value table and multiple writers would require expensive transactions, but with osmos, operations like this are no more expensive than replacement, but still ACID.&lt;br /&gt;&lt;br /&gt;As you can see, keeping statistics for reporting (when the reports are queried infrequently relative to data collection) is one of the killer applications for a merge sort table.&lt;br /&gt;&lt;br /&gt;Among the possibilities for even wackier merge operations are:&lt;ul&gt;&lt;li&gt;Always return the earlier value. (What was the first value that occurred for this key?)&lt;/li&gt;&lt;li&gt;Take the union of two sets. (What are all the values that have occurred for this key?)&lt;/li&gt;&lt;li&gt;Take the intersection of two sets. (What values have always occurred for this key?)&lt;/li&gt;&lt;li&gt;Multiply two NxN matrices, e.g., to keep track of a series of rotations applied to a vector in R&lt;sup&gt;N&lt;/sup&gt;.&lt;/li&gt;&lt;li&gt;Compose a series of arbitrary operations applied to a space of replaceable objects, e.g.:&lt;pre&gt;&lt;br /&gt;  merge(_K, _, V) when ?is_value(V) -&gt;&lt;br /&gt;    V;&lt;br /&gt;  merge(_K, V, Op) when ?is_value(V),&lt;br /&gt;                        ?is_operation(Op) -&gt;&lt;br /&gt;    do_operation(Op, V);&lt;br /&gt;  merge(_K, V, Ops) when ?is_value(V),&lt;br /&gt;                         is_list(Ops) -&gt;&lt;br /&gt;    lists:foldl (fun (Op, V) -&gt;&lt;br /&gt;                   do_operation(Op, V)&lt;br /&gt;                 end,&lt;br /&gt;                 V,&lt;br /&gt;                 Ops);&lt;br /&gt;  merge(_K, Op1, Op2) when ?is_operation(Op1),&lt;br /&gt;                           ?is_operation(Op2) -&gt;&lt;br /&gt;    [Op1, Op2];&lt;br /&gt;  merge(_K, Op, Ops) when ?is_operation(Op),&lt;br /&gt;                          is_list(Ops) -&gt;&lt;br /&gt;    [Op | Ops];&lt;br /&gt;  merge(_K, Ops, Op) when is_list(Ops),&lt;br /&gt;                          ?is_operation(Op) -&gt;&lt;br /&gt;    Ops ++ [Op];&lt;br /&gt;  merge(_K, Ops1, Ops2) when is_list(Ops1),&lt;br /&gt;                             is_list(Ops2) -&gt;&lt;br /&gt;    Ops1 ++ Ops2.&lt;br /&gt;&lt;/pre&gt;The values could be employee records, and the operations could be things like, &amp;ldquo;change street address to X,&amp;rdquo; &amp;ldquo;increase salary by Y%.&amp;rdquo; Using this pattern, you can get extremely cheap transactional safety for any single-key operation, as long as your merge function implements it.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h4&gt;API&lt;/h4&gt;The basic API is quite simple:&lt;pre&gt;&lt;br /&gt;  {ok, Table} = osmos:open(Table, [{directory, D}, {format, F}])&lt;br /&gt;&lt;/pre&gt;to open a table named Table with the format F in the directory D;&lt;pre&gt;&lt;br /&gt;  ok = osmos:write(Table, Key, Value)&lt;br /&gt;&lt;/pre&gt;to write a record to the table;&lt;pre&gt;&lt;br /&gt;  case osmos:read(Table, Key) of&lt;br /&gt;    {ok, Value} -&gt; ...;&lt;br /&gt;    not_found   -&gt; ...&lt;br /&gt;  end&lt;br /&gt;&lt;/pre&gt;to read the record for a key; and&lt;pre&gt;&lt;br /&gt;  ok = osmos:close(Table)&lt;br /&gt;&lt;/pre&gt;to close the table.&lt;br /&gt;&lt;br /&gt;You can also iterate over a range of keys in chunks using osmos:select_range/5 and osmos:select_continue/3. The results from a select provide a consistent snapshot of the table, meaning that the results always reflect the contents of the table at the time of the original call to select_range. (In other words, any writes that happen between subsequent calls to select_continue won't affect the results.)&lt;br /&gt;&lt;br /&gt;A table format is a record with the following fields:&lt;ul&gt;&lt;li&gt;&lt;tt&gt;block_size::integer()&lt;/tt&gt;: block size of the table in bytes, controlling the size of disk reads (which are always whole blocks), and the fanout of the on-disk search trees.&lt;/li&gt;&lt;li&gt;&lt;tt&gt;key_format::#osmos_format{}&lt;/tt&gt;: on-disk format for keys. (A pair of functions to convert some set of terms to binaries and back.)&lt;/li&gt;&lt;li&gt;&lt;tt&gt;key_less::(KeyA, KeyB) -&gt; bool()&lt;/tt&gt;: comparison function defining the order of keys in the table. Takes two native-format keys, and returns true if the first argument is less than the second argument.&lt;/li&gt;&lt;li&gt;&lt;tt&gt;value_format::#osmos_format{}&lt;/tt&gt;: on-disk format for values.&lt;/li&gt;&lt;li&gt;&lt;tt&gt;merge::(Key, EarlierValue, LaterValue) -&gt; MergedValue&lt;/tt&gt;: the merge function described above.&lt;/li&gt;&lt;li&gt;&lt;tt&gt;short_circuit::(Key, Value) -&gt; bool()&lt;/tt&gt;: function which allows searches of the table to be terminated early (short-circuited) if it can be determined from a record that any earlier records with the same key are irrelevant.&lt;/li&gt;&lt;li&gt;&lt;tt&gt;delete::(Key, Value) -&gt; bool()&lt;/tt&gt;: function controlling when records are deleted from the table.&lt;/li&gt;&lt;/ul&gt;There are several pre-canned formats available from the function osmos_table_format:new/3, or you can build your own &lt;tt&gt;#osmos_table_format{}&lt;/tt&gt; record as needed.&lt;br /&gt;&lt;h4&gt;Performance&lt;/h4&gt;The file &lt;a href="http://code.google.com/p/osmos/source/browse/trunk/osmos/tests/osmos_benchmark_1.erl"&gt;tests/osmos_benchmark_1.erl&lt;/a&gt; in the source distribution contains a benchmark that uses variable-length binaries as keys (some more frequent than others, with an average length of 15 bytes), and nonnegative integers as values, encoded in 64 bits, where merging takes the sum of the two values. One process writes random keys and values as fast as it can, while another process reads random keys with a 10 ms sleep between reads.&lt;br /&gt;&lt;br /&gt;I ran the benchmark for 15 minutes on a 2.2 GHz dual-core MacBook, and got the following numbers:&lt;ul&gt;&lt;li&gt;5028735 total records written, for an average of 5587 writes/second (including the time to compute random keys, etc.)&lt;/li&gt;&lt;li&gt;an average of 109.8 microseconds per write call, which would mean a theoretical maximum write rate of 9111 writes/second (for this table format, machine, etc.)&lt;/li&gt;&lt;li&gt;the median time per write call was 30 microseconds, and the 90th percentile was 54 microseconds, indicating that the vast majority of writes are extremely quick&lt;/li&gt;&lt;li&gt;an average of 1900 microseconds (1.9 ms) per read call&lt;/li&gt;&lt;li&gt;the median time per read call was 979 microseconds, and the 90th percentile was 2567 microseconds&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;I reran the benchmark for 2 hours on the same machine, and got the following numbers:&lt;ul&gt;&lt;li&gt;17247676 total records written, for an average of 2396 writes/second&lt;/li&gt;&lt;li&gt;an average of 329.7 microseconds per write call, for a theoretical maximum write rate of 3033 writes/second&lt;/li&gt;&lt;li&gt;the median time per write call was 39 microseconds, and the 90th percentile was 88 microseconds&lt;/li&gt;&lt;li&gt;an average of 13076 microseconds (13 ms) per read call&lt;/li&gt;&lt;li&gt;the median time per read call was 6081 microseconds, and the 90th percentile was 34094 microseconds&lt;/li&gt;&lt;/ul&gt;The table had about 400 MB of data on the disk (in 7 files) at the end of the 2-hour run. This shows that read performance does start to suffer a bit as the amount of data on the disk grows, but writes remain very fast. (In fact, if there were no reads competing with writes, I wouldn't expect the write times to degrade even that much, since all that's happening synchronously during a write call is a buffered write to a journal file, and an insert into an in-memory tree.)&lt;br /&gt;&lt;hr&gt;&lt;a name="footnote1"&gt;[1]&lt;/a&gt; I have to thank my good friend Dave Benson for introducing me to these ideas, and the generalized merge sort table. His excellent library &lt;a href="http://gsk.sourceforge.net/"&gt;GSK&lt;/a&gt; provides a very similar table API (GskTable) for people who want to write single-threaded servers in C. (I ripped off several of his design ideas, including prefix compression and the variable-length integer encoding.)&lt;br /&gt;&lt;a name="footnote2"&gt;[2]&lt;/a&gt; Of course there may be implicit seeking due to the need to access multiple files at the same time. But for sequential access, the OS and disk hardware can mitigate this to a large degree as long as the number of  files isn't too large.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-6808756941789255761?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/6808756941789255761/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=6808756941789255761' title='14 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6808756941789255761'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6808756941789255761'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/07/osmos.html' title='osmos'/><author><name>Michael Radford</name><uri>http://www.blogger.com/profile/16558736208373025619</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>14</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-7865252711505916170</id><published>2009-06-17T15:53:00.000-07:00</published><updated>2009-06-17T15:58:39.162-07:00</updated><title type='text'>Keeping it simple with flatula</title><content type='html'>&lt;p&gt;Paul has &lt;a href="http://dukesoferl.blogspot.com/2008/08/scaling-mnesia-with-localcontent.html"&gt;blogged about overcoming mnesia performance issues&lt;/a&gt; in the past, but I don't think we've talked much about the ultimate strategy -- keeping data out of mnesia altogether.&lt;/p&gt;&lt;p&gt;When we first started serving ads, we stored information about every single ad impression in a huge mnesia database, for retrieval on click, and for building behavioral profiles. Almost needless to say, this didn't scale very far. We spent many a day last summer delving into mnesia internals, fixing corrupted table fragments after node crashes, bemoaning how long it took new nodes to join the schema under heavy load, and so on.&lt;/p&gt;&lt;p&gt;One of the simplest and most effective changes that got us out of this mess was not to store any per-impression data in mnesia at all -- instead, we started logging the data to flat files on disk, and storing a small pointer to the data in a cookie so we could read it back the next time we saw the user. Hardly a revolutionary solution . . . it's well-known that disk seeking is the enemy of performance. The hardest part was coming to realizations like, "Hmm, I guess we don't really care if a node goes down and we lose part of that data!"&lt;/p&gt;&lt;p&gt;We've open-sourced one of the main components that enabled this strategy: &lt;a href="http://code.google.com/p/flatula/"&gt;flatula&lt;/a&gt;, an Erlang application that manages write-once "tables" that are really just collections of flat files. It looks a bit like dets, except that it doesn't support deletions, updates, or iteration, and you can't make up the keys. But when you don't need those things, it's hard to imagine a more efficient way to store data.&lt;/p&gt;&lt;p&gt;If you'd like to learn more, there's a &lt;a href="http://code.google.com/p/flatula/wiki/FlatulaHowTo"&gt;brief tutorial&lt;/a&gt; on the &lt;a href="http://code.google.com/p/flatula"&gt;Google Code site&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-7865252711505916170?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/7865252711505916170/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=7865252711505916170' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/7865252711505916170'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/7865252711505916170'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/06/keeping-it-simple-with-flatula.html' title='Keeping it simple with flatula'/><author><name>Michael Radford</name><uri>http://www.blogger.com/profile/16558736208373025619</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-5637168639941991120</id><published>2009-06-10T15:31:00.001-07:00</published><updated>2009-08-09T12:56:02.330-07:00</updated><title type='text'>Let parse transform</title><content type='html'>So the problem of &lt;a href="http://www.erlang.org/cgi-bin/ezmlm-cgi?4:sss:44510:200906:jollkhpiabpdbdpbhjoc#b"&gt;intermediate variable naming&lt;/a&gt; came up again on&lt;span style="text-decoration: underline;"&gt;&lt;/span&gt; erlang questions.&lt;br /&gt;&lt;br /&gt;&lt;div class="message"&gt; &lt;div class="rfc822hdr"&gt;&lt;hr /&gt; &lt;span class="subject"&gt;&lt;em&gt;Subject:&lt;/em&gt;Versioned variable names&lt;/span&gt;&lt;br /&gt;&lt;em&gt;From:&lt;/em&gt; Attila Rajmund Nohl&lt;br /&gt;&lt;em&gt;Date:&lt;/em&gt; Tue, 9 Jun 2009 17:12:34 +0200&lt;br /&gt;&lt;/div&gt; &lt;hr /&gt; &lt;pre&gt;Hello!&lt;br /&gt;&lt;br /&gt;I think there wasn't any grumbling this month about the&lt;br /&gt;immutable local variables in Erlang, so here's real world&lt;br /&gt;code I've found just today:&lt;br /&gt;&lt;/pre&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;% Take away underscore and replace with hyphen&lt;br /&gt;MO1 = re:replace(MO, "_", "-", [{return, list}, global]),&lt;br /&gt;MO2 = toupper(MO1),&lt;br /&gt;% Replace zeros&lt;br /&gt;MO3 = re:replace(MO2,&lt;br /&gt;                 "RX0",&lt;br /&gt;                 "RXO",&lt;br /&gt;                 [{return, list}, global]),&lt;br /&gt;% Insert hyphen if missing&lt;br /&gt;MO5 = case re:run(MO3, "-", [{capture, none}]) of&lt;br /&gt;  nomatch -&gt;&lt;br /&gt;    insert_hyphen(MO3);&lt;br /&gt;  match -&gt;&lt;br /&gt;MO3&lt;br /&gt;end,&lt;br /&gt;&lt;/pre&gt;&lt;pre&gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;hr /&gt;&lt;span style="text-decoration: underline;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;Mikael Pettersson &lt;a href="http://www.erlang.org/cgi-bin/ezmlm-cgi?4:mss:44467:200906:jollkhpiabpdbdpbhjoc"&gt;pointed out&lt;/a&gt; that this really has less to do with immutable local variables and more to do with the lack of a &lt;a href="http://en.wikipedia.org/wiki/Scheme_%28programming_language%29#Variables"&gt;let expression&lt;/a&gt;.  That was insightful, and since a let expression can be considered syntactic sugar for a lambda expression, I realized that a parse transform could provide let like functionality.  Let is a reserved keyword in Erlang so I used lyet instead.&lt;br /&gt;&lt;br /&gt;Essentially the parse transform rewrites&lt;br /&gt;&lt;pre class="brush:erlang"&gt;lyet:lyet (A = B, C)&lt;/pre&gt; as&lt;br /&gt;&lt;pre class="brush:erlang"&gt;(fun (A) -&gt; C end) (B)&lt;/pre&gt; so the above code could be rewritten as&lt;br /&gt;&lt;pre class="brush:erlang"&gt;&lt;br /&gt;Result = lyet:lyet (&lt;br /&gt;  % Take away underscore and replace with hyphen&lt;br /&gt;  MO = re:replace(MO, "_", "-", [{return, list}, global]),&lt;br /&gt;  MO = toupper(MO),&lt;br /&gt;  % Replace zeros&lt;br /&gt;  MO = re:replace(MO,&lt;br /&gt;                  "RX0",&lt;br /&gt;                  "RXO",&lt;br /&gt;                  [{return, list}, global]),&lt;br /&gt;  % Insert hyphen if missing&lt;br /&gt;  case re:run(MO, "-", [{capture, none}]) of&lt;br /&gt;    nomatch -&gt;&lt;br /&gt;      insert_hyphen(MO);&lt;br /&gt;    match -&gt;&lt;br /&gt;      MO&lt;br /&gt;  end),&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You must provide at least one argument to lyet:lyet.  All but the last argument to lyet:lyet must be an assignment, and the last argument has to be a single expression (but you can use begin and end for a block of expressions inside the lyet).  As you can see above, you can reuse a variable name across the assignment arguments to lyet:lyet.  You can even use lyet:lyet on the right hand side of the assignments, or as part of the expression argument.  Some examples of usage are present in the &lt;a href="http://code.google.com/p/fwtemplates/source/browse/trunk/lyet/tests/testlyet.erl"&gt;unit test&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Update&lt;/span&gt;: per Ulf's suggestion, the parse transform also recognizes the local call let_ in addition to the remote call lyet:lyet.  It definitely looks nicer with let_.&lt;br /&gt;&lt;br /&gt;The software is available on &lt;a href="http://code.google.com/p/fwtemplates/downloads/list?can=2&amp;amp;q=lyet&amp;amp;colspec=Filename+Summary+Uploaded+Size+DownloadCount"&gt;Google code&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-5637168639941991120?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/5637168639941991120/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=5637168639941991120' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/5637168639941991120'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/5637168639941991120'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/06/let-parse-transform.html' title='Let parse transform'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-8644277686531956866</id><published>2009-05-19T15:35:00.000-07:00</published><updated>2009-05-26T10:56:50.054-07:00</updated><title type='text'>Automatic .app file generation</title><content type='html'>At our startup, we have our own &lt;a href="http://code.google.com/p/fwtemplates"&gt;build system (framewerk)&lt;/a&gt; and our &lt;a href="http://code.google.com/p/erlrc"&gt;deployment framework (erlrc)&lt;/a&gt; which play well together.  As I learned at the Bay Area Erlang Factory, mostly people have their own processes already, so what they want to extract some of the useful functionality and incorporate it into their way of doing things.  This led to exposing &lt;a href="http://dukesoferl.blogspot.com/2009/05/automatic-appup-file-generation.html"&gt;automatic .appup file generation&lt;/a&gt; from erlrc in a reusable fashion; that technique requires .app files to be correct which is why we automatically generate them in framewerk.  To compliment, I've isolated our automatic .app file generation and released it in a standalone form.&lt;br /&gt;&lt;br /&gt;The escript is called &lt;a href="http://fwtemplates.googlecode.com/files/fwte-makeappfilev4"&gt;fwte-makeappfile&lt;/a&gt;, and it basically takes a set of Erlang source code which comprise an application and does three things for you: 1) attempts to automatically discover registered processes, 2) attempts to automatically discover all the module names, and 3) attempts to automatically discover the start module for the application (if present).  You can override these choices if you don't like them.  Here's an example of how it works:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;% ./fwte-makeappfile --application nitrogen --description 'Nitrogen Web Framework' --version '0.2009.05.11' ~/src/nitrogen-git/src/**/*.erl&lt;br /&gt;{application,nitrogen,&lt;br /&gt;         [{description,"Nitrogen Web Framework"},&lt;br /&gt;          {vsn,"0.2009.05.11"},&lt;br /&gt;          {modules,[action_add_class,action_alert,action_animate,&lt;br /&gt;                    action_appear,action_buttonize,action_comet_start,&lt;br /&gt;                    action_confirm,action_disable_selection,action_effect,&lt;br /&gt;                    action_event,action_fade,action_hide,&lt;br /&gt;                    action_jquery_effect,action_remove_class,&lt;br /&gt;                    action_script,action_show,action_toggle,&lt;br /&gt;                    action_validate,action_validation_error,element_bind,&lt;br /&gt;                    element_body,element_br,element_button,&lt;br /&gt;                    element_checkbox,element_datepicker_textbox,&lt;br /&gt;                    element_draggable,element_dropdown,element_droppable,&lt;br /&gt;                    element_file,element_flash,element_google_chart,&lt;br /&gt;                    element_gravatar,element_h1,element_h2,element_h3,&lt;br /&gt;                    element_h4,element_hidden,element_hr,element_image,&lt;br /&gt;                    element_inplace_textbox,element_label,&lt;br /&gt;                    element_lightbox,element_link,element_list,&lt;br /&gt;                    element_listitem,element_literal,element_p,&lt;br /&gt;                    element_panel,element_password,element_placeholder,&lt;br /&gt;                    element_radio,element_radiogroup,&lt;br /&gt;                    element_rounded_panel,element_singlerow,&lt;br /&gt;                    element_sortblock,element_sortitem,element_span,&lt;br /&gt;                    element_spinner,element_table,element_tablecell,&lt;br /&gt;                    element_tableheader,element_tablerow,element_template,&lt;br /&gt;                    element_textarea,element_textbox,element_upload,&lt;br /&gt;                    element_value,element_windex,element_wizard,mirror,&lt;br /&gt;                    nitrogen,nitrogen_file,nitrogen_inets_app,&lt;br /&gt;                    nitrogen_mochiweb_app,nitrogen_project,&lt;br /&gt;                    nitrogen_yaws_app,sync,validator_confirm_password,&lt;br /&gt;                    validator_custom,validator_is_email,&lt;br /&gt;                    validator_is_integer,validator_is_required,&lt;br /&gt;                    validator_js_custom,validator_max_length,&lt;br /&gt;                    validator_min_length,web_x,wf,wf_bind,wf_cache,&lt;br /&gt;                    wf_cache_server,wf_comet,wf_continuation,wf_convert,&lt;br /&gt;                    wf_counter,wf_email,wf_handle,wf_handle_firstrequest,&lt;br /&gt;                    wf_handle_postback,wf_handle_postback_multipart,&lt;br /&gt;                    wf_http_basic_auth,wf_inets,wf_init,wf_mochiweb,&lt;br /&gt;                    wf_multipart,wf_path,wf_platform,wf_platform_inets,&lt;br /&gt;                    wf_platform_mochiweb,wf_platform_yaws,wf_query,&lt;br /&gt;                    wf_redirect,wf_render,wf_script,wf_session,&lt;br /&gt;                    wf_session_server,wf_session_sup,wf_state,wf_tags,&lt;br /&gt;                    wf_utils,wf_validation,wf_yaws]},&lt;br /&gt;          {registered,[wf_session_server,wf_session_sup]},&lt;br /&gt;          {applications,[kernel,stdlib]},&lt;br /&gt;          {env,[]}]}&lt;br /&gt;.&lt;/blockquote&gt;We use this in our build system, where the .app file is always automatically generated, but the developer can set overrides if the autodetection is f-ing up.  I recommend this as a general strategy: you need to be able to manually specify for edge cases, but you don't want to count on your developers maintaining the routine cases correctly.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-8644277686531956866?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/8644277686531956866/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=8644277686531956866' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/8644277686531956866'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/8644277686531956866'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/05/automatic-app-file-generation.html' title='Automatic .app file generation'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-547737946266095007</id><published>2009-05-12T22:56:00.001-07:00</published><updated>2009-05-17T10:25:10.490-07:00</updated><title type='text'>Erlang R12B-5 for Fink (Mac OS/X)</title><content type='html'>It looks like &lt;a href="http://pdb.finkproject.org/pdb/package.php/erlang-otp"&gt;Fink&lt;/a&gt; is a bit behind the Erlang releases.  Here's an &lt;a href="http://fwtemplates.googlecode.com/files/erlang-otp.info"&gt;erlang-otp.info&lt;/a&gt; file and an &lt;a href="http://fwtemplates.googlecode.com/files/erlang-otp.patch"&gt;erlang-otp.patch&lt;/a&gt; file.  You can put these in &lt;span style="font-family:courier new;"&gt;/sw/fink/dists/unstable/main/finkinfo/languages&lt;/span&gt; and then do a &lt;span style="font-family:courier new;"&gt;fink install erlang-otp&lt;/span&gt; .  I'll ping the maintainer to get these added to Fink.&lt;br /&gt;&lt;br /&gt;When you &lt;span style="font-family:courier new;"&gt;fink install&lt;/span&gt; these right now, you'll get prompted because the tarballs are not on the fink mirrors, so you'll have to select "download from original source URL".&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Update&lt;/span&gt;: if you have pcre installed it can confuse the build.  You can use &lt;a href="http://fwtemplates.googlecode.com/files/erlang-otp.info.pcre"&gt;erlang-otp.info.pcre&lt;/a&gt; instead.  Rename this to &lt;span style="font-family: courier new;"&gt;erlang-otp.info&lt;/span&gt; in the &lt;span style="font-family:courier new;"&gt;/sw/fink/dists/unstable/main/finkinfo/languages&lt;/span&gt; directory.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-547737946266095007?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/547737946266095007/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=547737946266095007' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/547737946266095007'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/547737946266095007'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/05/erlang-r12b-5-for-fink-mac-osx.html' title='Erlang R12B-5 for Fink (Mac OS/X)'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-3831222930504241096</id><published>2009-05-09T22:18:00.000-07:00</published><updated>2009-05-09T22:34:24.886-07:00</updated><title type='text'>Automatic .appup file generation</title><content type='html'>&lt;a href="http://dukesoferl.blogspot.com/2009/04/erlrc-erlang-factory-talk.html"&gt;My talk&lt;/a&gt; at the &lt;a href="http://www.erlang-factory.com/conference/SFBayAreaErlangFactory2009"&gt;Erlang Factory&lt;/a&gt; covered several topics, but mostly people were only interested in the last 5 minutes, where I talk about how we automatically generate appup files when we are installing our software.  (Makes you wonder how to do a &lt;a href="http://venturehacks.com/articles/minimum-viable-product"&gt;minimum viable talk&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;I created a &lt;a href="http://code.google.com/p/erlrc/source/browse/trunk/src/erlrc-makeappup"&gt;direct (escript) interface&lt;/a&gt; to the automatic appup generation that doesn't require you use erlrc to manage your nodes (although, you do need erlrc installed so the escript can access the magic functions).  The idea is you invoke this at the right time during whatever installation procedure you are using, and redirect the output to create the appup file (e.g., either when building a new release from an old release, or "just in time" when you are installing a new application).  When things go right, it looks something like this:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;% ./erlrc-makeappup erlrc 0.1.2 0.2.0 /usr/lib/erlang/lib/erlrc-0.1.2 /Users/pmineiro/tmp/usr/lib/erlang/lib/erlrc-0.2.0&lt;br /&gt;{"0.2.0",&lt;br /&gt; [{"0.1.2",&lt;br /&gt;   [{load_module,erlrc},&lt;br /&gt;    {load_module,erlrc_boot},&lt;br /&gt;    {load_module,erlrc_lib},&lt;br /&gt;    {load_module,erlrcdynamic},&lt;br /&gt;    {update,erlrcsup,supervisor},&lt;br /&gt;    {load_module,erlrcsupnotify},&lt;br /&gt;    {load_module,release_handler_1}]}],&lt;br /&gt; [{"0.1.2",&lt;br /&gt;   [{load_module,release_handler_1},&lt;br /&gt;    {load_module,erlrcsupnotify},&lt;br /&gt;    {update,erlrcsup,supervisor},&lt;br /&gt;    {load_module,erlrcdynamic},&lt;br /&gt;    {load_module,erlrc_lib},&lt;br /&gt;    {load_module,erlrc_boot},&lt;br /&gt;    {load_module,erlrc}]}]}&lt;/blockquote&gt;That's the appup file that erlrc generates for itself when it is being upgraded from version 0.1.2 to 0.2.0.  The (surprisingly simple) appup generation algorithm is &lt;a href="http://code.google.com/p/erlrc/wiki/ErlrcHowto#Automatic_.appup_file_generation"&gt;outlined in the documentation&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This is available starting in version 0.2.0 of erlrc which is &lt;a href="http://code.google.com/p/erlrc/"&gt;available on google code&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-3831222930504241096?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/3831222930504241096/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=3831222930504241096' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/3831222930504241096'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/3831222930504241096'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/05/automatic-appup-file-generation.html' title='Automatic .appup file generation'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-6661027044460632990</id><published>2009-05-01T10:30:00.000-07:00</published><updated>2009-05-01T10:40:03.993-07:00</updated><title type='text'>The Need for Language Specific Packaging</title><content type='html'>We use apt to manage all our software at our startup, and we like being able to manage components from a heterogeneous set of languages in a uniform way.  In other words, some of our software is in Erlang, some is not, and the apt (plus &lt;a href="http://code.google.com/p/erlrc"&gt;erlrc&lt;/a&gt;) lets us treat them similarly.  One might guess that I would be against an Erlang-specific packaging solution such as &lt;a href="http://www.erlware.org/"&gt;erlware&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Not so!  We need erlware and I love it.&lt;br /&gt;&lt;br /&gt;At Yahoo we had a proprietary package format that nobody outside Yahoo used.  However we could algorithmically convert any CPAN package to our internal package format, so in effect, we had access to all of CPAN.  Analogously, companies might use rpm or deb or something else entirely; but by releasing all Erlang software in a standard form, it should be possible to transduce.  Right now, when we get stuff from the universe, we end up repackaging them for debian and placing them into our private package archive.  Everybody does things slightly differently so it's difficult to generalize this process.&lt;br /&gt;&lt;br /&gt;Furthermore, there are exciting benefits to having a single place to look for open-source Erlang software which enforces project quality standards and provides a cross-platform way to start playing around with Erlang.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-6661027044460632990?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/6661027044460632990/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=6661027044460632990' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6661027044460632990'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6661027044460632990'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/05/need-for-language-specific-packaging.html' title='The Need for Language Specific Packaging'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-2594490530255153738</id><published>2009-04-29T10:08:00.000-07:00</published><updated>2009-05-01T11:44:41.485-07:00</updated><title type='text'>erlrc erlang factory talk</title><content type='html'>I'll be giving a talk about erlrc at the Erlang Factory.  erlrc is our scheme for integrating the Erlang VM with the OS package manager (we use deb, but it should be possible to integrate with rpm and others as well).&lt;br /&gt;&lt;br /&gt;The &lt;a href="http://code.google.com/p/erlrc"&gt;google code project&lt;/a&gt; contains the &lt;a href="http://erlrc.googlecode.com/files/erlrctalk.pptx"&gt;powerpoint slides&lt;/a&gt; as well as other &lt;a href="http://code.google.com/p/erlrc/wiki/ErlrcHowto"&gt;useful documentation&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In the talk I indicate that erlrc is componentized for embedding in other build systems and launch processes, and I also mention that we built our own automake-based build system  (which is used for all the languages we develop in, not just Erlang).   That system is called &lt;a href="http://code.google.com/p/fwtemplates/"&gt;framewerk&lt;/a&gt; and is also available from google code.&lt;br /&gt;&lt;br /&gt;I also claim that once your entire software state is represented in OS packages, simple and effective strategies for deployment and provisioning emerge.  Two simple tools that we've developed along these lines are ec2-do and apt-snapshot.&lt;br /&gt;&lt;br /&gt;ec2-do is an escript that provides rolling window execution of an arbitrary command across a set of ec2 machines (typically, an ec2 group).  When we want to launch we do something like this to sync all the machines with the package archive,&lt;br /&gt;&lt;blockquote&gt;ec2-do -g production -m 1000 -b -d apt-get update&lt;/blockquote&gt;and then something like this to install the package&lt;br /&gt;&lt;blockquote&gt;ec2-do -g production -m 3 -b -d apt-get install &amp;lt;newpackage&amp;gt;&lt;/blockquote&gt;ec2-do is available from the &lt;a href="http://erlrc.googlecode.com/files/ec2-do-0.1.4.tar.gz"&gt;erlrc downloads page&lt;/a&gt;&lt;a href="http://erlrc.googlecode.com/files/ec2-do-0.1.4.tar.gz"&gt;.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;apt-snapshot is even simpler than ec2-do (just a shell script), but very useful for provisioning when using deb packages.  It can associate the current package state of a server with a symbolic tag which can later be restored (possibly from another host).  To create a tag we do something like&lt;br /&gt;&lt;blockquote&gt;apt-snapshot create &amp;lt;tag&amp;gt;&lt;/blockquote&gt;and to restore it later we do&lt;br /&gt;&lt;blockquote&gt;apt-snapshot restore &amp;lt;host&amp;gt;:&amp;lt;tag&amp;gt;&lt;/blockquote&gt;We use this in three distinct ways.  First, we have a cron job creating regular snapshots on every machine with timestamp tag names in case of snafu (to allow us to rollback).  Second, when we launch something particularly risky we create a symbolic tag manually for rollback.  Third, when we start up new machines, we get them in the correct state quickly by tagging one of the currently running machines and then telling the new machines to restore that tag from that host (our EC2 images are sensitive to the extra arguments passed to ec2-run-instance, but you can also just use ec2-do for this purpose, since restoring a tag is idempotent).&lt;br /&gt;&lt;br /&gt;apt-snapshot is available from the &lt;a href="http://erlrc.googlecode.com/files/apt-snapshot"&gt;erlrc downloads page&lt;/a&gt;&lt;a href="http://erlrc.googlecode.com/files/apt-snapshot"&gt;.&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-2594490530255153738?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/2594490530255153738/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=2594490530255153738' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/2594490530255153738'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/2594490530255153738'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2009/04/erlrc-erlang-factory-talk.html' title='erlrc erlang factory talk'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-887772750525041697</id><published>2008-08-06T18:19:00.001-07:00</published><updated>2008-08-06T18:54:10.115-07:00</updated><title type='text'>scaling mnesia with local_content</title><content type='html'>so we've been spending the last two weeks trying to scale our mnesia application for a whole bunch of new traffic that's about to show up.  previously we had to run alot of ec2 instances since we were using ets (RAM-based) storage; once we solved that we started to wonder how many machines we could turn off.&lt;br /&gt;&lt;br /&gt;initial indications were disappointing in terms of capacity.  none of cpu, network, memory, or disk i/o seemed particularly taxed.  for instance, even though raw sequential performance on an ec2 instance can hit 100mb/s, we were unable to hit above 1mb/s of disk utilization.  one clue: we did get about 6mb/s of disk utilization when we started a node from scratch.  in that case, 100 parallel mnesia_loader processes grab table copies from remote nodes.  thus even under an unfavorable access pattern (writing 100 different tables at once), the machines were capable of more.&lt;br /&gt;&lt;br /&gt;one problem we suspected was the registered process mnesia_tm, since all table updates go through this process.  the mnesia docs do say that it is "primarily intended to be a memory-resident database".  so one thought was that mnesia_tm was hanging out waiting for disk i/o to finish and this was introducing latency and lowering throughput; with ets tables, updates are cpu bound so this design would not be so problematic.  (we already have tcerl using the async_thread_pool, but that just means the emulator can do something else, not the mnesia_tm process in particular).  thus, we added an option to &lt;a href="http://code.google.com/p/tcerl/"&gt;tcerl&lt;/a&gt; to not wait for the return value of the linked-in driver before returning from an operation (and therefore not to check for errors).  that didn't have much impact on i/o utilization.&lt;br /&gt;&lt;br /&gt;we'd long ago purged transactions from the serving path, but we use sync_dirty alot.  we thought maybe mnesia_tm was hanging out waiting for acknowledgements from remote mnesia_tm processes and this was introducing latency and lowering throughput.  so we tried async_dirty.  well that helped, except that under load the message queue length for the mnesia_tm process began to grow and eventually we would have run out of memory.&lt;br /&gt;&lt;br /&gt;then we discovered local_content, which causes a table to have the same definition everywhere, but different content.  as a side effect, replication is short-circuited.  so with very minor code changes we tested this out and saw a significant performance improvement.  of course, we couldn't do this to every table we have; only for data for which we were ok with losing if the node went down.  however it's neat because now there are several types of data that can be managed within mnesia, in order of expense:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;transactional data.  distributed transactions are extremely expensive, but sometimes necessary.&lt;/li&gt;&lt;li&gt;highly available data.  when dirty operations are ok, but multiple copies of the data have to be kept, because the data should persist across node failures.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;useful data.  dirty operations are ok, and it's ok to lose some of the data if a node fails.&lt;/li&gt;&lt;/ol&gt;the &lt;a href="http://www1.erlang.org/doc/efficiency_guide/tablesDatabases.html"&gt;erlang efficiency guide&lt;/a&gt; says "For non-persistent database storage, prefer Ets tables over Mnesia local_content tables.", i.e., bypass mnesia for fastest results.  so we might do that, but right now it's convenient to have these tables acting like all our other tables.&lt;br /&gt;&lt;br /&gt;interestingly, i/o utilization didn't go up that much even though overall capacity improved alot.  we're writing about 1.5 mb/s now to local disks.  instead we appear cpu bound now; we don't know why yet.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-887772750525041697?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/887772750525041697/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=887772750525041697' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/887772750525041697'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/887772750525041697'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/08/scaling-mnesia-with-localcontent.html' title='scaling mnesia with local_content'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-6495775998059610457</id><published>2008-06-23T21:09:00.000-07:00</published><updated>2008-06-23T21:25:59.318-07:00</updated><title type='text'>Tokyocabinet and Mnesia</title><content type='html'>As &lt;a href="http://www.wagerlabs.com/blog/2008/06/mnesia-unlimited.html"&gt;Daisy&lt;/a&gt; has already indicated, it is now possible to plug &lt;a href="http://code.google.com/p/mnesiaex"&gt;arbitrary storage strategies into Mnesia&lt;/a&gt;.  For those who are familiar with mnesia_access, this is different; mnesia_access only covers reads and writes, not schema manipulations, and has &lt;a href="http://www.erlang.org/pipermail/erlang-questions/2008-April/034642.html"&gt;other deficiencies&lt;/a&gt; that it render it useless for adding a new storage type in practice (what mnesia_access is great for is changing the semantics of mnesia operations, e.g., mnesia_frag).  This project lets you make tables that are essentially indistinguishable from the built-in mnesia table types (ram_copies, disc_copies, disc_only_copies).&lt;br /&gt;&lt;br /&gt;Anyway our goal was to get a good on-disk ordered_set table type, since we've found ordered_sets very useful for the kinds of problems we're solving but we're tired of being limited by memory.  After looking around for a while &lt;a href="http://tokyocabinet.sourceforge.net/"&gt;Tokyocabinet&lt;/a&gt; emerged as our favorite for underlying implementation.  We considered BDB and libmysql, but Tokyocabinet seemed simple and faster, and had a more accommodating license.  So we ported Tokyocabinet to Erlang and then used the above storage API to connect to Mnesia.&lt;br /&gt;&lt;br /&gt;As a side benefit Tokyocabinet might also be preferred to dets even for set-type applications because of the lack of file size limit and high performance.  Tokyocabinet actually has a set-type storage strategy that we'd like to define an Erlang Term Store for, but as of this post, the set-type store doesn't support cursor positioning based upon a key, which makes the implementation of next tedious (although not impossible).  So I'm waiting on the author to add that call; if that happens, we could have a nicer on-disk set-type table as well.&lt;br /&gt;&lt;br /&gt;Everything is available on google code: &lt;a href="http://code.google.com/p/mnesiaex"&gt;mnesiaex&lt;/a&gt; (storage API) and &lt;a href="http://code.google.com/p/tcerl"&gt;tcerl&lt;/a&gt; (erlang port of Tokyocabinet).&lt;br /&gt;&lt;br /&gt;Also Daisy (aka Joel Reymont) is really nice to work with.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-6495775998059610457?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/6495775998059610457/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=6495775998059610457' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6495775998059610457'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6495775998059610457'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/06/tokyocabinet-and-mnesia.html' title='Tokyocabinet and Mnesia'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-1240176153328288267</id><published>2008-06-15T11:32:00.000-07:00</published><updated>2008-06-15T12:46:46.608-07:00</updated><title type='text'>generating app files</title><content type='html'>We automatically generate our .app files around here from analysis of the source code.  However this trick is buried inside &lt;a href="http://code.google.com/p/fwtemplates/wiki/FwTemplateErlangWalkthrough"&gt;fw-template-erlang&lt;/a&gt; which most (all?) people outside our group do not use.  I thought I'd highlight how it works.&lt;br /&gt;&lt;br /&gt;A typical &lt;a href="http://www.erlang.org/doc/design_principles/applications.html#appl_res_file"&gt;OTP application specification&lt;/a&gt; file looks like:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;{ application, walkenfs,&lt;br /&gt;[&lt;br /&gt;  { description, "Distributed filesystem." },&lt;br /&gt;  { vsn, "0.1.7" },&lt;br /&gt;  { modules, [ walkenfssup, walkenfsfragmentsrv, walkenfs, walkenfssrv ] },&lt;br /&gt;  { registered, [ walkenfsfragmentsrv ] },&lt;br /&gt;  { applications, [ kernel, stdlib   ] },&lt;br /&gt;  { mod, { walkenfs, [] } },&lt;br /&gt;  { env, [ { linked_in, false }, { mount_opts, "" }, { mount_point, "/walken" }, { prefix, walken }, { read_data_context, async_dirty }, { write_data_context, sync_dirty }, { read_meta_context, transaction }, { write_meta_context, sync_transaction }, { attr_timeout_ms, 0 }, { entry_timeout_ms, 0 }, { reuse_node, true }, { start_timeout, infinity }, { stop_timeout, 1800000 }, { init_fragments, 7 }, { init_copies, 3 }, { copy_type, n_disc_only_copies }, { buggy_ets, auto_detect }, { port_exit_stop, true } ] }&lt;br /&gt;&lt;br /&gt;]&lt;br /&gt;}.&lt;br /&gt;&lt;/pre&gt;There are alot of bits here that can filled in automatically.  It helps to think in terms of the template:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;{ application, @FW_PACKAGE_NAME@,&lt;br /&gt;[&lt;br /&gt;  { description, "@FW_PACKAGE_SHORT_DESCRIPTION@" },&lt;br /&gt;  { vsn, "@FW_PACKAGE_VERSION@" },&lt;br /&gt;  { modules, [ @FW_ERL_APP_MODULES@ ] },&lt;br /&gt;  { registered, [ @FW_ERL_APP_REGISTERED@ ] },&lt;br /&gt;  { applications, [ kernel, stdlib @FW_ERL_PREREQ_APPLICATIONS@ ] },&lt;br /&gt;  @FW_ERL_APP_MOD_LINE@&lt;br /&gt;  { env, @FW_ERL_APP_ENVIRONMENT@ }&lt;br /&gt;  @FW_ERL_APP_EXTRA@&lt;br /&gt;]&lt;br /&gt;}.&lt;br /&gt;&lt;/pre&gt;This is an "automake" style template where @VARIABLE@ indicates something that will be substituted.&lt;br /&gt;&lt;br /&gt;Now we can say where each of these templates values comes from when using fw-template-erlang:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;FW_PACKAGE_NAME: specified by the developer.  if you're using automake you've had to specify this already via &lt;a href="http://www.gnu.org/software/autoconf/manual/autoconf-2.57/html_node/autoconf_16.html"&gt;AC_INIT&lt;/a&gt;, so it's good to reuse that rather than require duplicate specification.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;FW_PACKAGE_SHORT_DESCRIPTION:  specified by the developer.&lt;/li&gt;&lt;li&gt;FW_PACKAGE_VERSION: specified by the developer; like the package name, if you're using automake this information is already contained in the arguments to AC_INIT so reuse that.&lt;/li&gt;&lt;li&gt;FW_ERL_APP_MODULES: generated by analysis of the source code directory.&lt;/li&gt;&lt;li&gt;FW_ERL_APP_REGISTERED: generated by analysis of the source code directory.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;FW_ERL_PREREQ_APPLICATIONS: specified by the developer.&lt;/li&gt;&lt;li&gt;FW_ERL_MOD_LINE: partially generated by analysis of the source code directory, partially specified by the developer (see below).&lt;/li&gt;&lt;li&gt;FW_ERL_APP_ENVIRONMENT: specified by the developer.&lt;/li&gt;&lt;li&gt;FW_ERL_APP_EXTRA: specified by the developer.  this is for unusual extra directives like included applications.&lt;/li&gt;&lt;/ol&gt;So the interesting bits come down to FW_ERL_APP_MODULES, FW_ERL_APP_REGISTERED, and FW_ERL_MOD_LINE being generated automatically via inspection of the source code.&lt;br /&gt;&lt;br /&gt;First, a note on intervention.  Anytime you are attempting to automate a task that was previous done by humans, it's helps to allow a human to override any of the automatic settings that you generate by default.  That way, you can be correct only 95% of the time and still be a huge timesaver.  In practice we've found that although it easy to come up with code examples that flummox the automatic strategies, such code is never actually written by people in the normal course of their work.  In fact, we've found that application files we get from the universe are often missing registered process names that our automatic strategy finds.  So empirically we're running at 100% correct so far, but fw-template-erlang still contains the capability to override any automatically generated value.&lt;br /&gt;&lt;br /&gt;Now the actual trick to computing all the stuff is that Erlang contains an Erlang compiler as a library. Here's an example of leveraging this to generate all the module names from a set of files; any file that contains a &lt;pre&gt;-fwskip()&lt;/pre&gt; directive will be ignored.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module (find_modules).&lt;br /&gt;-export ([ main/1 ]).&lt;br /&gt;&lt;br /&gt;is_skipped ([]) -&gt; false;&lt;br /&gt;is_skipped ([ { attribute, _, fwskip, _ } | _ ]) -&gt; true;&lt;br /&gt;is_skipped ([ _ | Rest ]) -&gt; is_skipped (Rest).&lt;br /&gt;&lt;br /&gt;print_module (Dir, F) -&gt;&lt;br /&gt;case compile:file (F, [ binary, 'E', { outdir, Dir } ]) of&lt;br /&gt;{ ok, Mod, { Mod, _, Forms } } -&gt;&lt;br /&gt;case is_skipped (Forms) of&lt;br /&gt;  true -&gt;&lt;br /&gt;    ok;&lt;br /&gt;  false -&gt;&lt;br /&gt;    port_command (get (three), io_lib:format ("~p~n", [ Mod ]))&lt;br /&gt;end;&lt;br /&gt;_ -&gt;&lt;br /&gt;ok&lt;br /&gt;end.&lt;br /&gt;&lt;br /&gt;main ([ Dir | Rest ]) -&gt;&lt;br /&gt;ok = file:make_dir (Dir),&lt;br /&gt;&lt;br /&gt;try&lt;br /&gt;  Three = open_port ({ fd, 0, 3 }, [ out ]),&lt;br /&gt;% ugh ... don't want to have to change all these function signatures,&lt;br /&gt;% so i'm gonna be dirty&lt;br /&gt;  put (three, Three),&lt;br /&gt;  lists:foreach (fun (F) -&gt; print_module (Dir, F) end, Rest)&lt;br /&gt;after&lt;br /&gt;  { ok, FileNames } = file:list_dir (Dir),&lt;br /&gt;  lists:foreach (fun (F) -&gt; file:delete (Dir ++ "/" ++ F) end, FileNames),&lt;br /&gt;  file:del_dir (Dir)&lt;br /&gt;end;&lt;br /&gt;main ([]) -&gt;&lt;br /&gt;Port = open_port ({ fd, 0, 2 }, [ out ]),&lt;br /&gt;port_command (Port, "usage: find-modules.esc tmpdir filename [filename ...]\n"),&lt;br /&gt;halt (1).&lt;br /&gt;&lt;/pre&gt;The output is on file descriptor 3 in order to isolate the desired output from various messages being output by the Erlang code.  This was originally an escript but to be compatible with older versions of Erlang we changed it to run as follows:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;#! /bin/sh&lt;br /&gt;&lt;br /&gt;# NB: this script is called find-modules.sh&lt;br /&gt;&lt;br /&gt;# unfortunately, escript is a recent innovation ...&lt;br /&gt;&lt;br /&gt;ERL_CRASH_DUMP=/dev/null&lt;br /&gt;export ERL_CRASH_DUMP&lt;br /&gt;&lt;br /&gt;erl -pa "${FW_ROOT}/share/fw/template/erlang/" -pa "${FW_ROOT}/share/fw.local/template/erlang" -pa "${top_srcdir}/fw/template/erlang/" -pa "${top_srcdir}/fw.local/template/erlang" -noshell -noinput -eval '&lt;br /&gt;find_modules:main (init:get_plain_arguments ()),&lt;br /&gt;halt (0).&lt;br /&gt;' -extra "$@" 3&gt;&amp;amp;1 &gt;/dev/null&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In practice this gets executed like&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;find src -name '*.erl' -print | xargs find-modules.sh "$tmpdir" |&lt;br /&gt;perl -ne 'chomp;&lt;br /&gt;     next if $seen{$_}++;&lt;br /&gt;     print ", " if $f;&lt;br /&gt;     print $_;&lt;br /&gt;     $f = 1;'&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Ok, that was the easy one!  In fact, the problem of finding all the module names seems so easy that one could be tempted to solve it with grep (hmmm: what about preprocesser directives ?).  However the point is that nothing parses Erlang as well as Erlang, so why reinvent the wheel.&lt;br /&gt;&lt;br /&gt;Computing the mod line in the application specification is just slightly harder.  Basically we scan source code for a module which has a &lt;pre&gt;-behaviour(application)&lt;/pre&gt; attribute and assume that is the start module.  The Erlang compiler allows behaviour to be (mis)spelt American-style so we allow that as well.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module (find_start_module).&lt;br /&gt;-export ([ main/1 ]).&lt;br /&gt;&lt;br /&gt;is_application ([]) -&gt; false;&lt;br /&gt;is_application ([ { attribute, _, behaviour, [ application ] } | _ ]) -&gt; true;&lt;br /&gt;is_application ([ { attribute, _, behavior, [ application ] } | _ ]) -&gt; true;&lt;br /&gt;is_application ([ _ | Rest ]) -&gt; is_application (Rest).&lt;br /&gt;&lt;br /&gt;is_skipped ([]) -&gt; false;&lt;br /&gt;is_skipped ([ { attribute, _, fwskip, _ } | _ ]) -&gt; true;&lt;br /&gt;is_skipped ([ _ | Rest ]) -&gt; is_skipped (Rest).&lt;br /&gt;&lt;br /&gt;find_start_module (_, []) -&gt;&lt;br /&gt;ok;&lt;br /&gt;find_start_module (Dir, [ F | Rest ]) -&gt;&lt;br /&gt;case compile:file (F, [ binary, 'E', { outdir, Dir } ]) of&lt;br /&gt;{ ok, Mod, { Mod, _, Forms } } -&gt;&lt;br /&gt;  case is_application (Forms) and not is_skipped (Forms) of&lt;br /&gt;    true -&gt;&lt;br /&gt;      port_command (get (three), io_lib:format ("~p~n", [ Mod ]));&lt;br /&gt;    false -&gt;&lt;br /&gt;      find_start_module (Dir, Rest)&lt;br /&gt;  end;&lt;br /&gt;_ -&gt;&lt;br /&gt;  find_start_module (Dir, Rest)&lt;br /&gt;end.&lt;br /&gt;&lt;br /&gt;main ([ Dir | Rest ]) -&gt;&lt;br /&gt;ok = file:make_dir (Dir),&lt;br /&gt;&lt;br /&gt;try&lt;br /&gt;Three = open_port ({ fd, 0, 3 }, [ out ]),&lt;br /&gt;% ugh ... don't want to have to change all these function signatures,&lt;br /&gt;% so i'm gonna be dirty&lt;br /&gt;put (three, Three),&lt;br /&gt;find_start_module (Dir, Rest)&lt;br /&gt;after&lt;br /&gt;{ ok, FileNames } = file:list_dir (Dir),&lt;br /&gt;lists:foreach (fun (F) -&gt; file:delete (Dir ++ "/" ++ F) end, FileNames),&lt;br /&gt;file:del_dir (Dir)&lt;br /&gt;end;&lt;br /&gt;main ([]) -&gt;&lt;br /&gt;Port = open_port ({ fd, 0, 2 }, [ out ]),&lt;br /&gt;port_command (Port, "usage: find-modules.esc tmpdir filename [filename ...]\n"),&lt;br /&gt;halt (1).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If there are no applications in the set of files being analyzed, we output nothing for the mod line in the application file, otherwise we output &lt;pre&gt;{ mod, { @FW_ERL_APP_START_MODULE@, @FW_ERL_APP_START_ARGS@ } },&lt;/pre&gt; where FW_ERL_APP_START_MODULE is the output of the above command and FW_ERL_APP_START_ARGS is indicated by the programmer (we use [] by default for FW_ERL_APP_START_ARGS and in practice never use the field).  Obviously, if multiple modules implementing the application behaviour are found then this will not work.  Our style has been to put each application we make in a separate directory to facilitate automatic analysis.&lt;br /&gt;&lt;br /&gt;Anyway now we can tackle the more challenging problem of finding registered processes.  There are many library calls that end up registering a process name; the ones we recognize are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;supervisor child specs that contain calls of the form&lt;/li&gt;&lt;ul&gt;&lt;li&gt;{ _, start, [ { local, xxx }, ... ] } -&gt; xxx being registered&lt;/li&gt;&lt;li&gt;{ _, start_link, [ { local, xxx }, ... ] } -&gt; xxx being registered&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;function calls of the form&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Module:start ({ local, xxx }, ...) -&gt; xxx being registered&lt;/li&gt;&lt;li&gt;Module:start_link ({ local, xxx }, ...) -&gt; xxx being registered&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;calls to erlang:register (xxx, ...) -&gt; xxx being registered&lt;/li&gt;&lt;/ul&gt;There's alot of code here but the general idea is the same:  walk the forms generated by compile and pattern match on one of the above cases, outputting discovered registered process names to file descriptor 3.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module (find_registered).&lt;br /&gt;-export ([ main/1 ]).&lt;br /&gt;&lt;br /&gt;%% looks like fun_clauses are the same as clauses (?)&lt;br /&gt;print_registered_fun_clauses (Clauses) -&gt;&lt;br /&gt;print_registered_clauses (Clauses).&lt;br /&gt;&lt;br /&gt;%% looks like icr_clauses are the same as clauses (?)&lt;br /&gt;print_registered_icr_clauses (Clauses) -&gt;&lt;br /&gt;print_registered_clauses (Clauses).&lt;br /&gt;&lt;br /&gt;print_registered_inits (Inits) -&gt;&lt;br /&gt;lists:foreach (fun ({ record_field, _, _, E }) -&gt;&lt;br /&gt;               print_registered_expr (E);&lt;br /&gt;                 (_) -&gt;&lt;br /&gt;               ok&lt;br /&gt;             end,&lt;br /&gt;             Inits).&lt;br /&gt;&lt;br /&gt;print_registered_upds (Upds) -&gt;&lt;br /&gt;lists:foreach (fun ({ record_field, _, _, E }) -&gt;&lt;br /&gt;               print_registered_expr (E);&lt;br /&gt;                 (_) -&gt;&lt;br /&gt;               ok&lt;br /&gt;             end,&lt;br /&gt;             Upds).&lt;br /&gt;&lt;br /&gt;% hmmm ... pretty sure patterns are not supposed to have side-effects ...&lt;br /&gt;print_registered_pattern (_) -&gt; ok.&lt;br /&gt;print_registered_pattern_group (_) -&gt; ok.&lt;br /&gt;&lt;br /&gt;print_registered_quals (Qs) -&gt;&lt;br /&gt;lists:foreach (fun ({ generate, _, P, E }) -&gt;&lt;br /&gt;               print_registered_pattern (P),&lt;br /&gt;               print_registered_expr (E);&lt;br /&gt;                 ({ b_generate, _, P, E }) -&gt;&lt;br /&gt;               print_registered_pattern (P),&lt;br /&gt;               print_registered_expr (E);&lt;br /&gt;                 (E) -&gt;&lt;br /&gt;               print_registered_expr (E)&lt;br /&gt;             end,&lt;br /&gt;             Qs).&lt;br /&gt;&lt;br /&gt;print_registered_expr ({ cons, _, H, T }) -&gt;&lt;br /&gt;print_registered_expr (H),&lt;br /&gt;print_registered_expr (T);&lt;br /&gt;print_registered_expr ({ lc, _, E, Qs }) -&gt;&lt;br /&gt;print_registered_expr (E),&lt;br /&gt;print_registered_quals (Qs);&lt;br /&gt;print_registered_expr ({ bc, _, E, Qs }) -&gt;&lt;br /&gt;print_registered_expr (E),&lt;br /&gt;print_registered_quals (Qs);&lt;br /&gt;&lt;br /&gt;%% Ok, here's some meat:&lt;br /&gt;%% the "supervisor child spec" rule&lt;br /&gt;%% { _, start, [ { local, xxx }, ... ] } -&gt; xxx being registered&lt;br /&gt;%% { _, start_link, [ { local, xxx }, ... ] } -&gt; xxx being registered&lt;br /&gt;print_registered_expr ({ tuple, _,&lt;br /&gt;                     Exprs=[ _,&lt;br /&gt;                       { atom, _, Func },&lt;br /&gt;                       { cons,&lt;br /&gt;                         _,&lt;br /&gt;                         { tuple, _, [ { atom, _, local },&lt;br /&gt;                                       { atom, _, Name } ] },&lt;br /&gt;                         _ } ] }) when (Func =:= start) or&lt;br /&gt;                                       (Func =:= start_link) -&gt;&lt;br /&gt;port_command (get (three), io_lib:format ("~p~n", [ Name ])),&lt;br /&gt;print_registered_exprs (Exprs);&lt;br /&gt;&lt;br /&gt;print_registered_expr ({ tuple, _, Exprs }) -&gt;&lt;br /&gt;print_registered_exprs (Exprs);&lt;br /&gt;print_registered_expr ({ record_index, _, _, E }) -&gt;&lt;br /&gt;print_registered_expr (E);&lt;br /&gt;print_registered_expr ({ record, _, _, Inits }) -&gt;&lt;br /&gt;print_registered_inits (Inits);&lt;br /&gt;print_registered_expr ({ record_field, _, E0, _, E1 }) -&gt;&lt;br /&gt;print_registered_expr (E0),&lt;br /&gt;print_registered_expr (E1);&lt;br /&gt;print_registered_expr ({ record, _, E, _, Upds }) -&gt;&lt;br /&gt;print_registered_expr (E),&lt;br /&gt;print_registered_upds (Upds);&lt;br /&gt;print_registered_expr ({ record_field, _, E0, E1 }) -&gt;&lt;br /&gt;print_registered_expr (E0),&lt;br /&gt;print_registered_expr (E1);&lt;br /&gt;print_registered_expr ({ block, _, Exprs }) -&gt;&lt;br /&gt;print_registered_exprs (Exprs);&lt;br /&gt;print_registered_expr ({ 'if', _, IcrClauses }) -&gt;&lt;br /&gt;print_registered_icr_clauses (IcrClauses);&lt;br /&gt;print_registered_expr ({ 'case', _, E, IcrClauses }) -&gt;&lt;br /&gt;print_registered_expr (E),&lt;br /&gt;print_registered_icr_clauses (IcrClauses);&lt;br /&gt;print_registered_expr ({ 'receive', _, IcrClauses }) -&gt;&lt;br /&gt;print_registered_icr_clauses (IcrClauses);&lt;br /&gt;print_registered_expr ({ 'receive', _, IcrClauses, E, Exprs }) -&gt;&lt;br /&gt;print_registered_icr_clauses (IcrClauses),&lt;br /&gt;print_registered_expr (E),&lt;br /&gt;print_registered_exprs (Exprs);&lt;br /&gt;print_registered_expr ({ 'try', _, Exprs0, IcrClauses0, IcrClauses1, Exprs1 }) -&gt;&lt;br /&gt;print_registered_exprs (Exprs0),&lt;br /&gt;print_registered_icr_clauses (IcrClauses0),&lt;br /&gt;print_registered_icr_clauses (IcrClauses1),&lt;br /&gt;print_registered_exprs (Exprs1);&lt;br /&gt;print_registered_expr ({ 'fun', _, Body }) -&gt;&lt;br /&gt;case Body of&lt;br /&gt;  { clauses, Cs } -&gt;&lt;br /&gt;    print_registered_fun_clauses (Cs);&lt;br /&gt;  _ -&gt;&lt;br /&gt;    ok&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;%% Ok, here's some meat:&lt;br /&gt;%% Module:start ({ local, xxx }, ...) -&gt; xxx being registered&lt;br /&gt;%% Module:start_link ({ local, xxx }, ...) -&gt; xxx being registered&lt;br /&gt;&lt;br /&gt;print_registered_expr ({ call,&lt;br /&gt;                     _,&lt;br /&gt;                     E={ remote, _, _, { atom, _, Func } },&lt;br /&gt;                     Exprs=[ { tuple, _, [ { atom, _, local },&lt;br /&gt;                                           { atom, _, Name } ] } | _ ] })&lt;br /&gt;                        when (Func =:= start) or&lt;br /&gt;                             (Func =:= start_link) -&gt;&lt;br /&gt;port_command (get (three), io_lib:format ("~p~n", [ Name ])),&lt;br /&gt;print_registered_expr (E),&lt;br /&gt;print_registered_exprs (Exprs);&lt;br /&gt;&lt;br /&gt;%% Ok, here's some meat:&lt;br /&gt;%% erlang:register (xxx, ...) -&gt; xxx being registered&lt;br /&gt;&lt;br /&gt;print_registered_expr ({ call,&lt;br /&gt;                     _,&lt;br /&gt;                     { remote,&lt;br /&gt;                       _,&lt;br /&gt;                       { atom, _, erlang },&lt;br /&gt;                       { atom, _, register } },&lt;br /&gt;                     Exprs=[ { atom, _, Name } | _ ] }) -&gt;&lt;br /&gt;port_command (get (three), io_lib:format ("~p~n", [ Name ])),&lt;br /&gt;print_registered_exprs (Exprs);&lt;br /&gt;&lt;br /&gt;print_registered_expr ({ call, _, E, Exprs }) -&gt;&lt;br /&gt;print_registered_expr (E),&lt;br /&gt;print_registered_exprs (Exprs);&lt;br /&gt;print_registered_expr ({ 'catch', _, E }) -&gt;&lt;br /&gt;print_registered_expr (E);&lt;br /&gt;print_registered_expr ({ 'query', _, E }) -&gt;&lt;br /&gt;print_registered_expr (E);&lt;br /&gt;print_registered_expr ({ match, _, P, E }) -&gt;&lt;br /&gt;print_registered_pattern (P),&lt;br /&gt;print_registered_expr (E);&lt;br /&gt;print_registered_expr ({ bin, _, PatternGrp }) -&gt;&lt;br /&gt;print_registered_pattern_group (PatternGrp);&lt;br /&gt;print_registered_expr ({ op, _, _, E }) -&gt;&lt;br /&gt;print_registered_expr (E);&lt;br /&gt;print_registered_expr ({ op, _, _, E0, E1 }) -&gt;&lt;br /&gt;print_registered_expr (E0),&lt;br /&gt;print_registered_expr (E1);&lt;br /&gt;print_registered_expr ({ remote, _, E0, E1 }) -&gt;&lt;br /&gt;print_registered_expr (E0),&lt;br /&gt;print_registered_expr (E1);&lt;br /&gt;print_registered_expr (_) -&gt;&lt;br /&gt;ok.&lt;br /&gt;&lt;br /&gt;print_registered_exprs (Exprs) -&gt;&lt;br /&gt;lists:foreach (fun (E) -&gt; print_registered_expr (E) end, Exprs).&lt;br /&gt;&lt;br /&gt;print_registered_clauses (Clauses) -&gt;&lt;br /&gt;lists:foreach (fun ({ clause, _, _, _, Exprs }) -&gt;&lt;br /&gt;                 print_registered_exprs (Exprs);&lt;br /&gt;                   (_) -&gt;&lt;br /&gt;                 ok&lt;br /&gt;               end,&lt;br /&gt;               Clauses).&lt;br /&gt;&lt;br /&gt;print_registered_forms (Forms) -&gt;&lt;br /&gt;lists:foreach (fun ({ function, _, _, _, Clauses }) -&gt;&lt;br /&gt;                 print_registered_clauses (Clauses);&lt;br /&gt;                   (_) -&gt;&lt;br /&gt;                 ok&lt;br /&gt;               end,&lt;br /&gt;               Forms).&lt;br /&gt;&lt;br /&gt;is_skipped ([]) -&gt; false;&lt;br /&gt;is_skipped ([ { attribute, _, fwskip, _ } | _ ]) -&gt; true;&lt;br /&gt;is_skipped ([ _ | Rest ]) -&gt; is_skipped (Rest).&lt;br /&gt;&lt;br /&gt;print_registered (Dir, F) -&gt;&lt;br /&gt;case compile:file (F, [ binary, 'E', { outdir, Dir } ]) of&lt;br /&gt;{ ok, _, { _, _, Forms } } -&gt;&lt;br /&gt;  case is_skipped (Forms) of&lt;br /&gt;    true -&gt;&lt;br /&gt;      ok;&lt;br /&gt;    false -&gt;&lt;br /&gt;      print_registered_forms (Forms)&lt;br /&gt;  end;&lt;br /&gt;_ -&gt;&lt;br /&gt;  ok&lt;br /&gt;end.&lt;br /&gt;&lt;br /&gt;main ([ Dir | Rest ]) -&gt;&lt;br /&gt;ok = file:make_dir (Dir),&lt;br /&gt;&lt;br /&gt;try&lt;br /&gt;Three = open_port ({ fd, 0, 3 }, [ out ]),&lt;br /&gt;% ugh ... don't want to have to change all these function signatures,&lt;br /&gt;% so i'm gonna be dirty&lt;br /&gt;put (three, Three),&lt;br /&gt;lists:foreach (fun (F) -&gt; print_registered (Dir, F) end, Rest)&lt;br /&gt;after&lt;br /&gt;{ ok, FileNames } = file:list_dir (Dir),&lt;br /&gt;lists:foreach (fun (F) -&gt; file:delete (Dir ++ "/" ++ F) end, FileNames),&lt;br /&gt;file:del_dir (Dir)&lt;br /&gt;end;&lt;br /&gt;main ([]) -&gt;&lt;br /&gt;Port = open_port ({ fd, 0, 2 }, [ out ]),&lt;br /&gt;port_command (Port, "usage: find-modules.esc tmpdir filename [filename ...]\n"),&lt;br /&gt;halt (1).&lt;br /&gt;&lt;/pre&gt;It's definitely possible to fool the above code.  For instance:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Hidden = therealname,&lt;br /&gt;erlang:register (Hidden, SomePid).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;However in practice developers tend to use either explicitly name their registered process or at worst obscure them with a macro (which, by going through the Erlang compiler, we get expanded for free), so this automatic strategy has been working very well.&lt;br /&gt;&lt;br /&gt;You can download &lt;a href="http://code.google.com/p/fwtemplates/downloads/list"&gt;fw-template-erlang&lt;/a&gt; from google code and get the complete source code from above plus some intuition about how it is called.  One thing that's been on our TODO list is to combine all these analysis into one pass on the source code; currently we pass through the source code three times, which is clearly suboptimal, although the process is speedy enough that it is only noticeable when analyzing larger projects (like, when we converted yaws and mnesia to fw-template-erlang for our internal use).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-1240176153328288267?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/1240176153328288267/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=1240176153328288267' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/1240176153328288267'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/1240176153328288267'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/06/generating-app-files.html' title='generating app files'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-6651319658700771609</id><published>2008-06-13T10:11:00.000-07:00</published><updated>2008-06-13T10:50:50.824-07:00</updated><title type='text'>Erlang is so clear.</title><content type='html'>Ah the clearness of erlang.&lt;br /&gt;&lt;br /&gt;Someone on the yaws list gets errors and asks:&lt;br /&gt;&lt;blockquote&gt;Can any one please explain me what this error is about and how to&lt;br /&gt;overcome this one?&lt;/blockquote&gt;and gets the reply&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;Well - it's fairly clear. It says:&lt;br /&gt;&lt;br /&gt;{undef,[{compile,file,&lt;br /&gt;               ["/home/phanikar/.yaws/yaws/default/m1.erl",&lt;br /&gt;                [binary,return_errors, ...&lt;br /&gt;&lt;br /&gt;That means that when .yaws page has been transformed into an&lt;br /&gt;.erl page, the yaws server cannot compile the generated .erl file&lt;br /&gt;You need to have the erlang compiler in the load path&lt;/blockquote&gt;I'm going to assume the author was being sarcastic about that being clear,  I mean I get that it couldn't compile something.. but..  no hint of what reasons it couldn't compile.&lt;br /&gt;&lt;br /&gt;maybe  to quote the awesome tude of of the dynamic window manager guys:&lt;br /&gt;&lt;blockquote&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;a href="http://www.suckless.org/wiki/dwm/"&gt;This keeps its userbase small and elitist. No novices asking stupid questions.&lt;/a&gt;&lt;/blockquote&gt;&lt;br /&gt;Well despite some of these problems erlang is super awesome.  It just needs a little more new blood to inject new attitudes of usability [ or some other language will evbentually just steal it's good ideas, which would be a shame.. we have them now.  ] Also new attitudes around  easier sharing of libraries. [  the &lt;a href="http://www.erlware.org/"&gt;erlware guys&lt;/a&gt; get that. ].&lt;br /&gt;&lt;br /&gt;I'm pretty proud to see Uncle Jesse and the gang here trying to contribute improvements all the time.  Better mnesia on disk tables in the works,  yaws and erlang patches,  &lt;a href="http://code.google.com/p/erlrc/wiki/ErlrcHowto"&gt;erlrc&lt;/a&gt;,  &lt;a href="http://code.google.com/p/fragmentron/"&gt;automatic mnesia fragmentation mangment&lt;/a&gt; and loads more.  I mean we're building a big and real distributed mnesia backed system  so we're learning a lot, and we're certainly not the only ones contributing lately.   &lt;br /&gt;&lt;br /&gt;So, as a novice who asks stupid questions,  I'm telling the rest of you out there not to give up if you run into a few weird error messages or attitudes from language scholars who have been so entrenched they don't see what seems weird at first.  Despite those few small speed bumps, there is some really powerful awesome stuff going on here.  So, don't let em be elitist barge in there and get some erlang for yourself.&lt;br /&gt;&lt;br /&gt;P.S. Pattern matching rules!!!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-6651319658700771609?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/6651319658700771609/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=6651319658700771609' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6651319658700771609'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6651319658700771609'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/06/erlang-is-so-clear.html' title='Erlang is so clear.'/><author><name>RoscoeOTPColtrain</name><uri>http://www.blogger.com/profile/07402696664361273638</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-7076198302203910448</id><published>2008-06-11T11:24:00.000-07:00</published><updated>2008-06-11T11:37:08.146-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='hot code loading'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>I'm in ur Erlangz, upgrading ur applicationz</title><content type='html'>Hot code loading is one of the sexiest features of Erlang; after all, who wants downtime?  If you're running mnesia, it's also very convenient, because as we've learned mnesia does not like to be shut down (more precisely, it makes no attempt to incrementally replicate changes across brief outages, instead doing full table (re)copying; something probably nobody cares about because they never turn it off).&lt;br /&gt;&lt;br /&gt;Erlang's hot code loading support consists of low level language and runtime features, and a high level strategy embodied by release handler.  The latter is a very Java-esque strategy of bundling up the complete new state of the system and upgrading  everything.  Without being normative let's just say that we enjoy using our OS package manager to manage the software on our boxes, and so we were interested in integrating Erlang hot code loading with the OS package manager.&lt;br /&gt;&lt;br /&gt;The result is &lt;a href="http://code.google.com/p/erlrc"&gt;erlrc&lt;/a&gt;, which when operating looks something like this:&lt;br /&gt;&lt;pre&gt;pmineiro@ub32srvvmw-199% sudo apt-get install egerl&lt;br /&gt;Reading package lists... Done&lt;br /&gt;Building dependency tree    &lt;br /&gt;Reading state information... Done&lt;br /&gt;The following packages will be upgraded:&lt;br /&gt;egerl&lt;br /&gt;1 upgraded, 0 newly installed, 0 to remove and 34 not upgraded.&lt;br /&gt;Need to get 0B/113kB of archives.&lt;br /&gt;After unpacking 0B of additional disk space will be used.&lt;br /&gt;WARNING: The following packages cannot be authenticated!  egerl&lt;br /&gt;Install these packages without verification [y/N]? y&lt;br /&gt;(Reading database ... 48221 files and directories currently installed.)&lt;br /&gt;Preparing to replace egerl 4.0.1 (using .../archives/egerl_4.1.0_all.deb) ...&lt;br /&gt;Unpacking replacement egerl ...&lt;br /&gt;erlrc-upgrade: Upgrading 'egerl': (cb8eec1a1b85ec017517d3e51c5aee7b) upgraded&lt;br /&gt;Setting up egerl (4.1.0) ...&lt;br /&gt;erlrc-start: Starting 'egerl': (cb8eec1a1b85ec017517d3e51c5aee7b) already_running&lt;br /&gt;&lt;/pre&gt;Basically, I issued an apt command, the result was to hot upgrade the corresponding Erlang application on the running Erlang node on the machine.&lt;br /&gt;&lt;br /&gt;In addition to integration with the OS package manager, erlrc also features an extensible boot procedure (basically, using a boot directory instead of a boot file to determine what to start, which makes packaging easier); and in addition erlrc will automatically generate appup files which correspond to most of the common cases outlined in the &lt;a href="http://www.erlang.org/doc/design_principles/appup_cookbook.html"&gt;appup cookbook&lt;/a&gt;.  Of course, Erlang packages made with &lt;a href="http://code.google.com/p/fwtemplates/wiki/FwTemplateErlangWalkthrough"&gt;fw-template-erlang&lt;/a&gt; are automatically instrumented for erlrc (but the instrumentation is inert if erlrc is not installed).&lt;br /&gt;&lt;br /&gt;If you're interested, there's &lt;a href="http://code.google.com/p/erlrc/wiki/ErlrcHowto"&gt;more extensive documentation&lt;/a&gt; on google code.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-7076198302203910448?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/7076198302203910448/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=7076198302203910448' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/7076198302203910448'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/7076198302203910448'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/06/im-in-ur-erlangz-upgrading-ur.html' title='I&apos;m in ur Erlangz, upgrading ur applicationz'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-6153154157988372718</id><published>2008-04-07T09:07:00.000-07:00</published><updated>2008-04-07T09:10:59.535-07:00</updated><title type='text'>Cooter to talk “Beautiful Concurrency with Erlang" at OSCON</title><content type='html'>Our buddy &lt;a href="http://kevin.scaldeferri.com/blog/"&gt;Cooter&lt;/a&gt; is going to be giving a talk at &lt;a href="http://en.oreilly.com/oscon2008/public/content/home"&gt;OSCON&lt;/a&gt; entitled&lt;br /&gt;&lt;br /&gt;&lt;a href="http://kevin.scaldeferri.com/blog/2008/03/27/Oscon2008Acceptance.html"&gt;“Beautiful Concurrency with Erlang"&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;Congrats cooter.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-6153154157988372718?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/6153154157988372718/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=6153154157988372718' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6153154157988372718'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/6153154157988372718'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/04/cooter-to-talk-beautiful-concurrency.html' title='Cooter to talk “Beautiful Concurrency with Erlang&quot; at OSCON'/><author><name>RoscoeOTPColtrain</name><uri>http://www.blogger.com/profile/07402696664361273638</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-9128415500579769832</id><published>2008-03-26T21:53:00.000-07:00</published><updated>2008-03-26T22:01:46.113-07:00</updated><title type='text'>Nodefinder improvements</title><content type='html'>Matthew Dempsky from &lt;a href="http://mochimedia.com/"&gt;Mochi&lt;/a&gt; mailed me some suggestions for &lt;a href="http://code.google.com/p/nodefinder/"&gt;nodefinder&lt;/a&gt;.  Now the multicast_ttl is an application parameter so multicast routing can be used; separate send and receive sockets are employed to ensure the right source address on BSD; and the discovery packet is signed using the cookie as a secret which prevents (hopeless but annoyingly logged) connect attempts to nodes with different cookies and also prevents a list_to_atom flood from packets that happen to match.&lt;br /&gt;&lt;br /&gt;Thanks Matt!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-9128415500579769832?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/9128415500579769832/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=9128415500579769832' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/9128415500579769832'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/9128415500579769832'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/03/nodefinder-improvements.html' title='Nodefinder improvements'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-2944493298231827001</id><published>2008-03-21T21:24:00.000-07:00</published><updated>2008-03-21T21:32:40.800-07:00</updated><title type='text'>network partition ... oops</title><content type='html'>well we had our first network partition on ec2 yesterday evening.  we think it was amazon doing maintenance because it happened in both our production and development clusters.&lt;br /&gt;&lt;br /&gt;it turns out things went pretty well considering we've done nothing yet to address network partitioning.  two islands formed, but never tried to join back up (it turns out we only try to connect nodes when &lt;a href="http://code.google.com/p/schemafinder"&gt;schemafinder&lt;/a&gt; starts).  after a while each island's schemafinder decided that the other island was dead and removed it from the schema.  so we ended up with two disjoint mnesia instances.  they each had complete data so they were able to serve.  of course, they were also creating data and diverging over time.  however right now we're not doing anything that important so it didn't really matter.  we just picked one island to survive and restarted the other ones after nuking their mnesia directories.&lt;br /&gt;&lt;br /&gt;however the experience has convinced us that we need to prioritize up our network partition recovery strategy.   the good news is that we have an out-of-band source of truth about what the set of nodes should be (via ec2-describe-instances), so it should be possible for us to detect a network partition has occurred.  the other good news is that we care more about high availability than transactional integrity so we'll be willing to set master nodes on tables based upon simple heuristics.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-2944493298231827001?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/2944493298231827001/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=2944493298231827001' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/2944493298231827001'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/2944493298231827001'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/03/network-partition-oops.html' title='network partition ... oops'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-8573121448551175810</id><published>2008-03-15T15:08:00.000-07:00</published><updated>2008-03-15T15:28:46.491-07:00</updated><title type='text'>walkenfs improvements</title><content type='html'>I've made some progress towards my goal of making &lt;a href="http://code.google.com/p/walkenfs"&gt;walkenfs&lt;/a&gt; usable by those who know nothing about &lt;a href="http://www.erlang.org"&gt;Erlang&lt;/a&gt; or &lt;a href="http://www.erlang.org/doc/apps/mnesia/index.html"&gt;Mnesia&lt;/a&gt;. &lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/schemafinder"&gt;schemafinder&lt;/a&gt; has been open sourced, which provides Mnesia auto-configure.&lt;/li&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/walkenfs/wiki/InstallationHowto#Hello_World"&gt;mount and umount work&lt;/a&gt; (although the mount line is very baroque), so no need to start an Erlang emulator explicitly.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;the distribution includes an &lt;a href="http://code.google.com/p/walkenfs/source/browse/trunk/src/walkenfs.example"&gt;example init script&lt;/a&gt; very close to the one we actually use.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Of course walkenfs could still have a showstopping bug in it.  In our recent use it was mostly &lt;a href="http://www.mjmwired.net/kernel/Documentation/filesystems/fuse.txt#91"&gt;fuse mount options&lt;/a&gt; that gave us trouble, and also the &lt;a href="http://forum.ntfs-3g.org/viewtopic.php?p=2196&amp;amp;sid=27fb1cad04a3308e067b816f5d15c69f"&gt;shared writeable mmap problem&lt;/a&gt; surfaced because &lt;a href="http://oss.oetiker.ch/rrdtool/"&gt;rrdtool&lt;/a&gt; uses that by default.  fortunately with rrdtool, it was a simple configure option to disable mmap.  We did have a data corruption scare in development but that turned out to be due to the default redundancy setting being 1 copy (changed that to 3, now that &lt;a href="http://code.google.com/p/fragmentron"&gt;fragmentron&lt;/a&gt; can initialize under infeasible conditions).  Otherwise, so far so good, knock on wood.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-8573121448551175810?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/8573121448551175810/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=8573121448551175810' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/8573121448551175810'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/8573121448551175810'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/03/walkenfs-improvements.html' title='walkenfs improvements'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-4546616778041992954</id><published>2008-03-12T21:39:00.000-07:00</published><updated>2008-03-12T22:48:19.524-07:00</updated><title type='text'>mnesia and ec2</title><content type='html'>&lt;a href="http://www.erlang.org/doc/apps/mnesia/index.html"&gt;Mnesia&lt;/a&gt; is Erlang's built-in multi-master distributed database.  It's one of the reasons why we chose Erlang for our startup.  And while it is mostly a great fit for &lt;a href="http://aws.amazon.com/ec2/"&gt;EC2&lt;/a&gt;, it needs some tweaks to work.  In particular we wanted the following to be automatic:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Discover nodes entering or leaving the system&lt;/li&gt;&lt;li&gt;Have them join the Mnesia database&lt;/li&gt;&lt;li&gt;Have them take responsibility for a portion of the data&lt;/li&gt;&lt;/ol&gt;We've already talked about and published solutions to the first problem (&lt;a href="http://code.google.com/p/nodefinder/"&gt;nodefinder&lt;/a&gt;) and third problem (&lt;a href="http://code.google.com/p/fragmentron/"&gt;fragmentron&lt;/a&gt;).  Now I just published a solution to the second problem on google code (&lt;a href="http://code.google.com/p/schemafinder/"&gt;schemafinder&lt;/a&gt;).  It took a while to get this part out partially because we kept finding small problems with it, but mostly because we're very busy.&lt;br /&gt;&lt;br /&gt;It's actually surprisingly complicated, given that at the root the way to add a node to a running database is to call &lt;a href="http://www.erlang.org/doc/man/mnesia.html#change_config-2"&gt;mnesia:change_config/2&lt;/a&gt; as mnesia:change_config (extra_db_nodes, NodeList).  However while adding nodes is easy removing nodes is harder.  Detecting nodes to remove is not that hard; we go with the (EC2-centric) strategy of periodically updating a record in a table to "stay alive" (and also a way to mark clean shutdown); nodes that fail to check in eventually are considered dead.  To handle multiple node failure we patch mnesia to provide mnesia_schema:del_table_copies/2, the multiple table analog to &lt;a href="http://www.erlang.org/doc/man/mnesia.html#del_table_copy-2"&gt;mnesia:del_table_copy/2&lt;/a&gt;.  To guard against rejoining the global schema after having been removed but without having removed one's mnesia database directory (this is a no-no), we check with the other database nodes to see if they think we've been removed.&lt;br /&gt;&lt;br /&gt;Even with all this complication, we have yet to address the problem of handling network partitioning automatically.  We'll see if we have time to address that before it bites us in the butt.&lt;br /&gt;&lt;br /&gt;Now &lt;a href="http://code.google.com/p/schemafinder/"&gt;available on google code&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-4546616778041992954?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/4546616778041992954/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=4546616778041992954' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/4546616778041992954'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/4546616778041992954'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/03/mnesia-and-ec2.html' title='mnesia and ec2'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-7353054232805989179</id><published>2008-03-05T21:13:00.000-08:00</published><updated>2008-03-05T21:24:51.606-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Mnesia'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>controlling mnesia db size</title><content type='html'>genexpire something quick that we rolled together to satisfy our paranoia about out-of-control databases (ram based dbs are especially worrisome). Basically, it periodically runs and enforces a maximum size for each database fragment (which depends upon how many fragments are on the node; in practice this means you get the space of your most crowded node in a distributed config. With &lt;a href="http://code.google.com/p/fragmentron"&gt;fragmentron&lt;/a&gt; things are balanced to within one fragment so with enough fragments this is ok.)&lt;br /&gt;&lt;br /&gt;Now &lt;a href="http://code.google.com/p/gencron"&gt;available on google code&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-7353054232805989179?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/7353054232805989179/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=7353054232805989179' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/7353054232805989179'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/7353054232805989179'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/03/controlling-mnesia-db-size.html' title='controlling mnesia db size'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-8486044466175865813</id><published>2008-03-02T22:53:00.001-08:00</published><updated>2008-03-02T22:57:23.605-08:00</updated><title type='text'>Procfs</title><content type='html'>Our next sprint's theme is "monitoring", so this weekend I thought I'd slap together a &lt;a href="http://en.wikipedia.org/wiki/Procfs"&gt;procfs&lt;/a&gt; style filesystem with fuserl.  I've got the contents of erlang:processes (), erlang:process_info (), erlang:ports (), erlang:port_info (), and erlang:system_info () now exposed in a filesystem format.  This is less useful than I envisioned on Friday, but it was pedagogical.&lt;br /&gt;&lt;br /&gt;Now &lt;a href="http://code.google.com/p/fuserl/"&gt;available on google code&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-8486044466175865813?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/8486044466175865813/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=8486044466175865813' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/8486044466175865813'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/8486044466175865813'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/03/procfs.html' title='Procfs'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-9094481637400661910</id><published>2008-02-24T11:06:00.000-08:00</published><updated>2008-02-24T11:18:27.220-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Automake'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>Erlang and Automake</title><content type='html'>There were some inquiries on the erlang-questions mailing list recently about using Gnu Automake with Erlang.  That reminded me to release a build system we've been using for development.&lt;br /&gt;&lt;br /&gt;My fascination with build systems predates my obsession with Erlang so it is more general than just Erlang.  It is organized around project templates, and there is &lt;a href="http://code.google.com/p/fwtemplates/wiki/FwTemplateErlangWalkthrough"&gt;Erlang project template&lt;/a&gt; that has been getting pretty well developed.  Every time we figure out a new best practice around Erlang development we incorporate it into the template so that all of our projects benefit.  So for instance now we have cover integration, eunit integration, and edoc integration.&lt;br /&gt;&lt;br /&gt;It is now &lt;a href="http://code.google.com/p/fwtemplates/"&gt;available on google code&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-9094481637400661910?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/9094481637400661910/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=9094481637400661910' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/9094481637400661910'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/9094481637400661910'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/02/erlang-and-automake.html' title='Erlang and Automake'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-7748975993536057023</id><published>2008-02-22T10:14:00.000-08:00</published><updated>2008-02-24T02:33:37.123-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>New book from Erlang Training</title><content type='html'>So, the guys Erlang Consulting and Training are making a &lt;a href="http://on-erlang.blogspot.com/2008/02/not-just-rumour.html"&gt;new book.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;and on the &lt;a href="http://www.erlang.org/mailman/listinfo/erlang-questions"&gt;erlang-questions&lt;/a&gt; mailing list:&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;On Feb 21, 2008, at 1:50 PM, Francesco Cesarini wrote:&lt;br /&gt;Our main goal is to cover Erlang in depth. Make sure that newbies struggling with recursion can easily understand it or developers being exposed to pattern matching for the first time use it optimally. The contents of the book are pretty much outlined by the introductory Erlang courses Jan-Henry and I have been giving in the last decade, and based on the knowledge and experience gained when teaching. We know [where] students struggle...&lt;/blockquote&gt; &lt;blockquote&gt;On Feb 21, 2008, at 4:29 PM, some guy wrote:&lt;br /&gt;It's just that, no matter how good it is, with Joe's book already out there - it's not going to be a must read for me.&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;blockquote&gt;On Feb 21, 2008, at 3:17 PM, someone else  wrote:&lt;br /&gt;Another book on just the Erlang programming language would not find a place on my bookshelf.&lt;/blockquote&gt;These responses  kind of made me upset.&lt;br /&gt;&lt;br /&gt;I applaud Francesco,  and Jan-Henry in getting another book out there. Obviously they have learned from their history areas where new comers to erlang have troubles, and they're making a book to plug that hole. They are of course asking you to look back on your experience coming into erlang, think about what concepts you had troubles with and let them know,  this would both help confirm the topics for their book, and possibly give them new topics that are perhaps different than they have been exposed to because they come from people&lt;br /&gt;who don't buy erlang training courses.&lt;br /&gt;&lt;br /&gt;Just because you don't need the book now doesn't mean you dont' have something to add.&lt;br /&gt;&lt;br /&gt;And sure, we all have frustrations about the OTP stuff,  it seems like it might be very very good, and then again it seems pretty opaque.    I encourage anyone reading this who knows more OTP to publish more stuff about it.&lt;br /&gt;&lt;br /&gt;[ I know I'm calling myself RoscoeOTPColtrain,  but right now I'm more roscoe than OTP, but perhaps I'll get there. ]&lt;br /&gt;&lt;br /&gt;&lt;aside&gt;&lt;br /&gt;As a scripter in the internet era I've always looked for high level tools that help me build scalable reliable systems as quickly and as error free as possible.&lt;br /&gt;&lt;br /&gt;As a manager I've grown to look for tools that help reduce the errors caused by teams of varying skill level trying to make deadlines that have little basis in reality.&lt;br /&gt;&lt;br /&gt;I believe erlang is a good fit. As mentioned on &lt;a href="http://www.erlang.org/mailman/listinfo/erlang-questions"&gt;erlang questions list&lt;/a&gt; It's missing libraries, [ we all should be addressing this. ] it's missing the ability to share code easily amongst the community. [ &lt;a href="http://www.erlware.org/"&gt;erlware&lt;/a&gt; guys are trying to address this. ] It's missing a lot of depth of documentation especially with respects to OTP.&lt;br /&gt;&lt;br /&gt;"see the OTP design principles", is it's own running gag around my office.   "Hey, where is the coffee maker?!?" "See the OTP design principles."&lt;br /&gt;&lt;br /&gt;Erlang provides some great tools to help regular code monkeys do great things,  with less errors.  It comes at a cost of learning some interesting programing paradigms which of course seem trivial to elitist programing apes.&lt;br /&gt;&lt;br /&gt;So, being a procedural and sometimes OO script monkey for most of my life. [ the joy of programing being denied to me cause I always end up managing. :) ] I found once I bridged the concepts of erlang I totally love it.  I feel that there are plenty of others out there who are like me that could also feel the same way.&lt;br /&gt;&lt;br /&gt;and last,  quoting &lt;a href="http://yarivsblog.com/articles/2007/01/11/erlyweb-tutorial-creating-a-simple-login-page/"&gt;yarivs&lt;/a&gt; "damn, I love pattern matching"&lt;br /&gt;&lt;/aside&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-7748975993536057023?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/7748975993536057023/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=7748975993536057023' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/7748975993536057023'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/7748975993536057023'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/02/so-guys-erlang-consulting-and-training.html' title='New book from Erlang Training'/><author><name>RoscoeOTPColtrain</name><uri>http://www.blogger.com/profile/07402696664361273638</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-5632038971175175980</id><published>2008-02-13T20:34:00.003-08:00</published><updated>2008-02-24T11:06:51.284-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>Automatic Node Discovery</title><content type='html'>One of the first problems we ran into on EC2 was having Erlang nodes find each other.   Erlang ships with a node discovery strategy based upon a &lt;a href="http://www.erlang.org/documentation/doc-5.4.13/lib/kernel-2.10.13/doc/html/net_adm.html"&gt;static hosts file&lt;/a&gt;.   On EC2, the idea is to start and stop nodes frequently; and EC2 will typically hand out different hostnames each time (I guess that repeats are possible but unlikely).  So a static hosts file won't work.&lt;br /&gt;&lt;br /&gt;Fortunately Erlang is ready for the set of nodes to change constantly, it is just not spelled out how to find them.  I've noticed this about Erlang: often an excellent design is coupled with a simplistic stub implementation.  It's the &lt;a href="http://www.amazon.com/Calculus-Vol-One-Variable-Introduction-Algebra/dp/0471000051"&gt;"Tommy"&lt;/a&gt; school of software: even the most casual observer is supposed to be able to figure out the next step.&lt;br /&gt;&lt;br /&gt;Initially we thought we'd use multicast UDP for discovery, which was extremely easy to do but then we discovered that &lt;a href="http://developer.amazonwebservices.com/connect/thread.jspa?threadID=19554&amp;amp;tstart=0"&gt;EC2 does not support multicast&lt;/a&gt;.  So then I overthought the problem and came up with a discovery protocol based upon using S3 as a blackboard.  That worked but was overkill, since just parsing the output of &lt;a href="http://docs.amazonwebservices.com/AWSEC2/2007-08-29/DeveloperGuide/CLTRG-describe-instances.html"&gt;ec2-describe-instances&lt;/a&gt; contains all the information needed, plus it allows &lt;a href="http://docs.amazonwebservices.com/AWSEC2/2007-08-29/DeveloperGuide/distributed-firewall-concepts.html"&gt;EC2 security groups&lt;/a&gt; to define the Erlang node groups.  This is convenient because the EC2 security group(s) can be determined dynamically from the instance metadata.&lt;br /&gt;&lt;br /&gt;As an aside, the &lt;a href="http://code.google.com/p/nodefinder/source/browse/trunk/ec2nodefinder/src/ec2nodefindersrv.erl#118"&gt;parsing of the output of ec2-describe-instances&lt;/a&gt; is done with a list comprehension and built-in pattern matching only (&lt;span style="font-family:arial;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;/span&gt;&lt;span style="font-style: italic;"&gt;parse_host_list/2&lt;/span&gt;&lt;/span&gt; and &lt;span style="font-style: italic;"&gt;parse_host_list/4&lt;/span&gt;).&lt;br /&gt;&lt;br /&gt;All three strategies are now &lt;a href="http://code.google.com/p/nodefinder/"&gt;available on google code&lt;/a&gt;.  If you're on EC2, ec2nodefinder is the recommendation.  Note, you can still in addition use a static hosts file (or any other node discovery strategy), e.g., if you have some non-EC2 hosts you'd like to connect as well.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-5632038971175175980?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/5632038971175175980/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=5632038971175175980' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/5632038971175175980'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/5632038971175175980'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/02/automatic-node-discovery.html' title='Automatic Node Discovery'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-8949539290890528993</id><published>2008-02-12T21:25:00.000-08:00</published><updated>2008-02-12T21:33:16.029-08:00</updated><title type='text'>howdy</title><content type='html'>we're the dukes.  basically 3 guys at a brand new startup (more about that later).  we want to have a go with Erlang and EC2 as the two seem to go together pretty nicely.  anyway we thought it'd be fun to blog about things as we figure them out, mostly about cloud computing with Erlang, but occasionally maybe about some dramatic startup stuff like not being able to make payroll.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-8949539290890528993?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/8949539290890528993/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=8949539290890528993' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/8949539290890528993'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/8949539290890528993'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/02/howdy.html' title='howdy'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6265608756663924839.post-8628523840526226568</id><published>2008-02-12T20:57:00.001-08:00</published><updated>2008-02-19T23:26:45.383-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='distributed filesystem'/><category scheme='http://www.blogger.com/atom/ns#' term='fuse'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>walkenfs on google code</title><content type='html'>We Dukes are into cloud computing.  We especially like our machines to be operationally symmetric so we don't have to think that hard.  Erlang makes that easy but unfortunately sometimes we have to deal with software not written in Erlang.&lt;br /&gt;&lt;br /&gt;In particular Fess started to put together a monitoring system lately using &lt;a href="http://oss.oetiker.ch/rrdtool/"&gt;rrdtool&lt;/a&gt; which is great but wants to operate on files.  We'd done all this work to make mnesia play nicely on EC2 (more on that later) and suddenly we seemed back at square one.  We considered storing data in mnesia and rendering files at the last minute for rrdtool; but then we noticed &lt;a href="http://fuse.sourceforge.net/"&gt;fuse&lt;/a&gt;.  Sweet!  Now we can store data in mnesia and make it look like a filesystem at the same time.&lt;br /&gt;&lt;br /&gt;First we needed &lt;a href="http://code.google.com/p/fuserl"&gt;Erlang bindings for fuse&lt;/a&gt;, so I wrote those.  That turned out to involve writing C code, a moderately frustrating experience that served as a nice reminder why we don't call ourselves the "Dukes of C".  Once the nasty bits were subdued, writing the actual Erlang code corresponding to a filesystem via mnesia was comparatively quick and painless.  I'm still trying to figure out the best way to support posix locking (right now, I'm looking for a satisfying way to have the locks release when an entire node dies unexpectedly), but everything else seems to be working.&lt;br /&gt;&lt;br /&gt;It still needs alot of polish.  As a filesystem one would expect a mkfs tool and the ability to put entries in /etc/fstab that work.  However &lt;a href="http://code.google.com/p/walkenfs/wiki/InstallationGuide#Hello_World"&gt;it's usable as part of an Erlang system&lt;/a&gt; right now.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6265608756663924839-8628523840526226568?l=dukesoferl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dukesoferl.blogspot.com/feeds/8628523840526226568/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6265608756663924839&amp;postID=8628523840526226568' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/8628523840526226568'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6265608756663924839/posts/default/8628523840526226568'/><link rel='alternate' type='text/html' href='http://dukesoferl.blogspot.com/2008/02/walkenfs-on-google-code.html' title='walkenfs on google code'/><author><name>Paul Mineiro</name><uri>http://www.blogger.com/profile/05439062526157173163</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry></feed>
