<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>The Onycloud Blog</title>
 <link href="http://onycloud.com/atom.xml" rel="self"/>
 <link href="http://onycloud.com"/>
 <updated>2011-08-19T16:10:45+08:00</updated>
 <id>http://onycloud.com</id>
 
 <entry>
   <title>Testing Web Applications with clj-webdriver</title>
   <link href="http://onycloud.com/2011/08/17/testing-web-app-with-clj-webdriver.html"/>
   <updated>2011-08-17T00:00:00+08:00</updated>
   <id>http://onycloud.com/2011/08/17/testing-web-app-with-clj-webdriver</id>
   <content type="html">&lt;p&gt;At OnyCloud, testing is an important part of our development activities. &lt;a href='https://trakrapp.com'&gt;Trakr&lt;/a&gt;, our issue tracking and project management app, has about 250 tests and nearly 1000 assertions. Those are all tests for the server code. However, for a JavaScript-heavy web application, only testing the server side covers at most half of the code base. The robustness of programs written in dynamic languages, JavaScript in particular, relies on testing. Therefore, we spent significant amount of time looking for good web testing solutions.&lt;/p&gt;

&lt;p&gt;We used to use &lt;a href='http://rubygems.org/gems/watir-webdriver'&gt;&lt;code&gt;watir-webdriver&lt;/code&gt;&lt;/a&gt;, which is a nice Ruby library to drive browsers, but we had to write the tests in Ruby. Since we use Clojure as the main language, we prefer to write our functional tests in Clojure too. &lt;a href='https://github.com/semperos/clj-webdriver'&gt;&lt;code&gt;clj-webdriver&lt;/code&gt;&lt;/a&gt; is a Clojure library for driving a web browser using Selenium-WebDriver as the backend. The project README provides detailed documentation. As an example for its usage in tests, the following test checks that the proper error message is shown when a user tries to register with an invalid email address.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='clojure'&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;invalid-email&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;b&lt;/span&gt; &lt;span class='nv'&gt;*browser*&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;get-url&lt;/span&gt; &lt;span class='nv'&gt;b&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;http://localhost:8887/trakr/&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;-&amp;gt; &lt;/span&gt;&lt;span class='nv'&gt;b&lt;/span&gt;
        &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;find-it&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;:href&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;/signup&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;})&lt;/span&gt;
        &lt;span class='nv'&gt;click&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;wait-until-present&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;find-it&lt;/span&gt; &lt;span class='nv'&gt;b&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;:name&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;email&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;}))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;-&amp;gt; &lt;/span&gt;&lt;span class='nv'&gt;b&lt;/span&gt;
        &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;find-it&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;:name&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;email&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;})&lt;/span&gt;
        &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;input-text&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;tester@localhost&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;-&amp;gt; &lt;/span&gt;&lt;span class='nv'&gt;b&lt;/span&gt;
        &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;find-it&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;:type&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;submit&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;})&lt;/span&gt;
        &lt;span class='nv'&gt;click&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;wait-until-present&lt;/span&gt;
     &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;find-it&lt;/span&gt; &lt;span class='nv'&gt;b&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;:id&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;flash-error&amp;quot;&lt;/span&gt; &lt;span class='nv'&gt;:text&lt;/span&gt; &lt;span class='o'&gt;#&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Invalid email&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;}))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When we trigger an action, we do not know how long it takes for the JavaScript code to execute and produce the expected effects. The helper macro &lt;code&gt;wait-until-present&lt;/code&gt; makes it easy to wait for some element to become visible on the page:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='clojure'&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defmacro &lt;/span&gt;&lt;span class='nv'&gt;wait-until-present&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;find-it-form&lt;/span&gt;
                              &lt;span class='nv'&gt;&amp;amp;&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;:keys&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;timeout&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;:or&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;timeout&lt;/span&gt; &lt;span class='mi'&gt;5&lt;/span&gt;&lt;span class='p'&gt;}}]&lt;/span&gt;
  &lt;span class='o'&gt;`&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;wait-until&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;and &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;exists?&lt;/span&gt; &lt;span class='nv'&gt;~find-it-form&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
         &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;visible?&lt;/span&gt; &lt;span class='nv'&gt;~find-it-form&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='nv'&gt;:timeout&lt;/span&gt; &lt;span class='nv'&gt;~timeout&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;wait-until&lt;/code&gt; is a more general macro to wait for some predicate to become true. It repeatedly evaluates the predicate until it is true. When the predicate evaluates to false, the macro waits for an exponentially increasing interval, until the timeout is hit.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='clojure'&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defmacro &lt;/span&gt;&lt;span class='nv'&gt;wait-once&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;pred&lt;/span&gt; &lt;span class='nv'&gt;ms-to-wait&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
  &lt;span class='o'&gt;`&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;if &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;try&lt;/span&gt; &lt;span class='nv'&gt;~pred&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;catch&lt;/span&gt; &lt;span class='nv'&gt;Exception&lt;/span&gt; &lt;span class='nv'&gt;_&lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt; &lt;span class='nv'&gt;false&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
     &lt;span class='nv'&gt;true&lt;/span&gt;
     &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;do&lt;/span&gt;
       &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;Thread/sleep&lt;/span&gt; &lt;span class='nv'&gt;~ms-to-wait&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;false&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defmacro &lt;/span&gt;&lt;span class='nv'&gt;wait-until&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;pred&lt;/span&gt; &lt;span class='nv'&gt;&amp;amp;&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;:keys&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;timeout&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;:or&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;timeout&lt;/span&gt; &lt;span class='mi'&gt;5&lt;/span&gt;&lt;span class='p'&gt;}}]&lt;/span&gt;
  &lt;span class='o'&gt;`&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;ms&lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='mi'&gt;1000&lt;/span&gt; &lt;span class='nv'&gt;~timeout&lt;/span&gt;&lt;span class='p'&gt;)]&lt;/span&gt;
     &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;loop &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;interval&lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;
            &lt;span class='nv'&gt;remaining&lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt; &lt;span class='nv'&gt;ms&lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
       &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;when &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;&amp;lt;= &lt;/span&gt;&lt;span class='nv'&gt;remaining&lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;throw&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;Exception&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;Timeout reached!&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
       &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;when-not &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;wait-once&lt;/span&gt; &lt;span class='nv'&gt;~pred&lt;/span&gt; &lt;span class='nv'&gt;interval&lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
         &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;recur&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='nv'&gt;interval&lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;- &lt;/span&gt;&lt;span class='nv'&gt;remaining&lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt; &lt;span class='nv'&gt;interval&lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt;&lt;span class='p'&gt;))))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</content>
 </entry>
 
 <entry>
   <title>A few new features for Trakr</title>
   <link href="http://onycloud.com/2011/07/29/a-few-new-features.html"/>
   <updated>2011-07-29T00:00:00+08:00</updated>
   <id>http://onycloud.com/2011/07/29/a-few-new-features</id>
   <content type="html">&lt;p&gt;Hello, we have just rolled out a few new features for &lt;a href='https://trakrapp.com'&gt;Trakr&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;We have added a &lt;strong&gt;user setting page&lt;/strong&gt; where you can change your email and password.&lt;/p&gt;

&lt;p&gt;&lt;img src='/images/setting-page-nav.png' alt='image' /&gt;&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;You can now &lt;strong&gt;watch public projects&lt;/strong&gt;. By &amp;#8220;public&amp;#8221; we mean&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everybody (including unregistered users) can view the issue list and every issue detail.&lt;/li&gt;

&lt;li&gt;All registered users can add new issues (report bug) to public projects and edit issues added by themselves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How can I make my project public? Go to project setting page and press the &amp;#8220;Make my project public&amp;#8221; button.&lt;/p&gt;

&lt;p&gt;How can I publicize my public project? Spread your project URL!&lt;/p&gt;

&lt;p&gt;How can people remember my public project URL? After people land on your public project, they will see a &amp;#8220;Watch&amp;#8221; button.&lt;/p&gt;

&lt;p&gt;&lt;img src='/images/project-watching-button.png' alt='image' /&gt;&lt;/p&gt;

&lt;p&gt;If they click on the button, they will be able to track your project on their dashboards afterwards.&lt;/p&gt;

&lt;p&gt;&lt;img src='/images/project-watching.png' alt='image' /&gt;&lt;/p&gt;

&lt;p&gt;Try out a public project &lt;a href='https://trakrapp.com/trakr/a#projects/trakr-feedback/issues'&gt;trakr-feedback&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;A news stream flowing too fast is harmful to your mental health. We used to generate too many news updates, because every single change of an issue attribute will result in an individual news update. Now we have changed the mechanism on issue detail page so that &lt;strong&gt;changes are aggregated&lt;/strong&gt; and pushed to news stream in one shot.&lt;/p&gt;

&lt;p&gt;&lt;img src='/images/change-aggregated.png' alt='image' /&gt;&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;No more page flickering on switching project news stream.&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;Many bug fixes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Send your feedback by adding issues at &lt;a href='https://trakrapp.com/trakr/a#projects/trakr-feedback/issues'&gt;trakr-feedback&lt;/a&gt;.&lt;/p&gt;</content>
 </entry>
 
 <entry>
   <title>Trakr Updated: A New UI and Various Enhancements</title>
   <link href="http://onycloud.com/2011/07/18/trakr-updates.html"/>
   <updated>2011-07-18T00:00:00+08:00</updated>
   <id>http://onycloud.com/2011/07/18/trakr-updates</id>
   <content type="html">&lt;p&gt;We have just pushed out a new version of &lt;a href='https://trakrapp.com'&gt;Trakr&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This update includes an overhaul of the user interface. The new UI is a lot cleaner and more consistent. We hope you like it.&lt;/p&gt;

&lt;p&gt;&lt;img src='/images/rel-2011-07-18-stream.png' alt='image' /&gt;&lt;/p&gt;

&lt;p&gt;On the issue details page, attachments and status can only be modified in edit mode now, which is more consistent with the rest of the page. We also made various other usability enhancements over the last week. Those changes are the prelude for some major improvements we are planning.&lt;/p&gt;

&lt;p&gt;&lt;img src='/images/rel-2011-07-18-list.png' alt='image' /&gt;&lt;/p&gt;

&lt;p&gt;If you do not already have a Trakr account, &lt;a href='https://trakrapp.com'&gt;sign up for free now&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have any feedback, please do not hesitate to &lt;a href='mailto:feedback@trakrapp.com'&gt;contact us&lt;/a&gt;.&lt;/p&gt;</content>
 </entry>
 
 <entry>
   <title>MapReduce with MongoDB and Clojure</title>
   <link href="http://onycloud.com/2011/07/13/mapreduce-with-mongodb-and-clojure.html"/>
   <updated>2011-07-13T00:00:00+08:00</updated>
   <id>http://onycloud.com/2011/07/13/mapreduce-with-mongodb-and-clojure</id>
   <content type="html">&lt;p&gt;A few days ago, we decided to create a dashboard in order to better visualize some statistics of our production systems. One important function is to plot the average latency as a time-series graph, so we can see the trend over time. Since &lt;a href='http://www.mongodb.org/'&gt;MongoDB&lt;/a&gt; implemented &lt;a href='http://en.wikipedia.org/wiki/MapReduce'&gt;MapReduce&lt;/a&gt;, and we store our logs in MongoDB, MapReduce seems a natural fit for log analysis.&lt;/p&gt;

&lt;p&gt;One issue with MongoDB&amp;#8217;s implementation of MapReduce is that no matter what language you use, you have to pass JavaScript code as strings to MongoDB. Storing code written in another language as strings in a program is &amp;#8230; inelegant, to say the least.&lt;/p&gt;

&lt;p&gt;Fortunately, Clojure being a &lt;a href='http://en.wikipedia.org/wiki/Homoiconicity'&gt;homoiconic language&lt;/a&gt;, it is relatively easy to transform Clojure forms into code snippets of other languages using Clojure itself in the same program. In other words, it is possible to embed JavaScript programs in a Clojure program without actually seeing any JavaScript syntax. There are already a number of libraries, with different level of maturity, that allow you to transform Clojure forms to JavaScript. I haven&amp;#8217;t done an extensive survey, but &lt;a href='https://github.com/kriyative/clojurejs/'&gt;ClojureJS&lt;/a&gt; is good enough for our purpose.&lt;/p&gt;

&lt;p&gt;The following the mapper to collect the latency numbers in the request log so that they are keyed by date:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='clojure'&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;req-latency-mapper&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;js&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;fn &lt;/span&gt;&lt;span class='p'&gt;[]&lt;/span&gt;
        &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;time&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:time&lt;/span&gt; &lt;span class='nv'&gt;this&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
              &lt;span class='nv'&gt;date&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;do&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;setHours&lt;/span&gt; &lt;span class='nv'&gt;time&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
                       &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;setMinutes&lt;/span&gt; &lt;span class='nv'&gt;time&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
                       &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;setSeconds&lt;/span&gt; &lt;span class='nv'&gt;time&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
                       &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;setMilliseconds&lt;/span&gt; &lt;span class='nv'&gt;time&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
                       &lt;span class='nv'&gt;time&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
              &lt;span class='nv'&gt;latency_ms&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:latency_ms&lt;/span&gt; &lt;span class='nv'&gt;this&lt;/span&gt;&lt;span class='p'&gt;)]&lt;/span&gt;
          &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;emit&lt;/span&gt; &lt;span class='nv'&gt;date&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;:num_requests&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;:sum_latency_ms&lt;/span&gt; &lt;span class='nv'&gt;latency_ms&lt;/span&gt;&lt;span class='p'&gt;})))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;js&lt;/code&gt; is a macro defined in ClojureJS that takes a Clojure form and returns JavaScript code as a string.&lt;/p&gt;

&lt;p&gt;Then define a reducer to compute the sum of latency and the number of requests in each day.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='clojure'&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;req-latency-day-reducer&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;js&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;fn &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;date&lt;/span&gt; &lt;span class='nv'&gt;vals&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
        &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;result&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;:num_requests&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;:sum_latency_ms&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;}]&lt;/span&gt;
          &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;forEach&lt;/span&gt; &lt;span class='nv'&gt;vals&lt;/span&gt;
                    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;fn &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;val&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
                      &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;set!&lt;/span&gt; &lt;span class='nv'&gt;result&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;num_requests&lt;/span&gt;
                            &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;+ &lt;/span&gt;&lt;span class='nv'&gt;result&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;num_requests&lt;/span&gt; &lt;span class='nv'&gt;val&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;num_requests&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
                      &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;set!&lt;/span&gt; &lt;span class='nv'&gt;result&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;sum_latency_ms&lt;/span&gt;
                            &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;+ &lt;/span&gt;&lt;span class='nv'&gt;result&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;sum_latency_ms&lt;/span&gt; &lt;span class='nv'&gt;val&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;sum_latency_ms&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;
          &lt;span class='nv'&gt;result&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We also need a finalizer to compute the average.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='clojure'&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;average-finalizer&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;js&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;fn &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;date&lt;/span&gt; &lt;span class='nv'&gt;val&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
        &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='nv'&gt;:keys&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;num_requests&lt;/span&gt; &lt;span class='nv'&gt;sum_latency_ms&lt;/span&gt;&lt;span class='p'&gt;]}&lt;/span&gt; &lt;span class='nv'&gt;val&lt;/span&gt;
              &lt;span class='nv'&gt;average&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;/ &lt;/span&gt;&lt;span class='nv'&gt;sum_latency_ms&lt;/span&gt; &lt;span class='nv'&gt;num_requests&lt;/span&gt;&lt;span class='p'&gt;)]&lt;/span&gt;
          &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;set!&lt;/span&gt; &lt;span class='nv'&gt;val&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;average_latency_ms&lt;/span&gt; &lt;span class='nv'&gt;average&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
          &lt;span class='nv'&gt;val&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Finally, we call &lt;code&gt;map-reduce&lt;/code&gt;, which is a wrapper around the MongoDB Java API recently implemented in &lt;a href='https://github.com/aboekhoff/congomongo'&gt;CongoMongo&lt;/a&gt;.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='clojure'&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;gen-daily-average-req-latency&lt;/span&gt; &lt;span class='p'&gt;[]&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;map-reduce&lt;/span&gt; &lt;span class='nv'&gt;:oc_request_log&lt;/span&gt;
              &lt;span class='nv'&gt;req-latency-mapper&lt;/span&gt;
              &lt;span class='nv'&gt;req-latency-day-reducer&lt;/span&gt;
              &lt;span class='nv'&gt;:oc_average_daily_req_latency&lt;/span&gt;
              &lt;span class='nv'&gt;:finalize&lt;/span&gt; &lt;span class='nv'&gt;average-finalizer&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now the job is done, with a nice and clean Clojure program.&lt;/p&gt;</content>
 </entry>
 
 <entry>
   <title>Trakr Updated</title>
   <link href="http://onycloud.com/2011/07/12/trakr-updated.html"/>
   <updated>2011-07-12T00:00:00+08:00</updated>
   <id>http://onycloud.com/2011/07/12/trakr-updated</id>
   <content type="html">&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Issue status is now color coded.&lt;/p&gt;

&lt;p&gt;&lt;img src='http://onycloud.com/images/status-color.png' alt='image' /&gt;&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;When a milestone is selected, its completion status is shown.&lt;/p&gt;

&lt;p&gt;&lt;img src='http://onycloud.com/images/milestone-progress.png' alt='image' /&gt;&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;Various bug fixes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href='http://twitter.com/Onycloud'&gt;Follow @Onycloud&lt;/a&gt;&lt;/p&gt;</content>
 </entry>
 
 <entry>
   <title>Trakr Public Beta</title>
   <link href="http://onycloud.com/2011/06/28/trakr-public-beta.html"/>
   <updated>2011-06-28T00:00:00+08:00</updated>
   <id>http://onycloud.com/2011/06/28/trakr-public-beta</id>
   <content type="html">&lt;p&gt;We are happy to announce the public availability of &lt;a href='https://trakrapp.com/'&gt;Trakr&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Trakr is an easy-to-use and powerful issue tracking system for team and individuals who want to focus on delivering great products. Whether you are a one-person shop or a team of hundreds, Trakr takes care of the mechanics in issue tracking and let you focus on what you are good at.&lt;/p&gt;

&lt;p&gt;&lt;a href='https://trakrapp.com'&gt;Sign up for free&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Trakr is free for all users during the public beta and will be always free for public projects. We will announce a pricing plan after the beta. Our pricing will be very competitive to similar products on the market, and even if you do not want to stay with us (we hope you do!), you will have plenty of time to export all your data.&lt;/p&gt;</content>
 </entry>
 

</feed>

