Calculate PI with syslog-ng

Origin story

We just realized that syslog-ng has a nice milestone as it reached the 3.14 version. I wanted to create something to celebrate it. There were a few ideas – still not forgotten, but in the end I though I do not want to extend syslog-ng with functionality, rather I want to create something within the current boundaries.

In the past few days also I got thinking about the configuration, and what you can achieve with it. Reaching the 3.14 milestone was a perfect opportunity to create a configuration that makes syslog-ng calculate the value of π.

The result

Here is a possible solution that actually approximates the value multiplied by 1000000000. The solution has a few interesting aspects, which could be explored in details later.
You can also download it from the github project: pi.conf

@version: 3.141592647


block root pi(iteration(), digits()) {

options { time-reopen(1); };

log {
   source { internal(); };

   filter { message("syslog-ng starting up"); };

   rewrite { set("1" value("i")); };
   rewrite { set("1" value("n")); };
   rewrite { set("-1" value("direction")); };
   rewrite { set("$(* 3 `digits`)" value("pi")); };

   destination {
     unix-stream("/tmp/loop"
                 persist-name("start")
                 template("$(format-welf --scope nv-pairs)\n"));
   };
};


log {
  source  { unix-stream("/tmp/loop" flags(no-parse)); };
  
  parser  { kv-parser(); };

  rewrite { set("$(* ${direction} -1)" value("direction")); };

  rewrite { set("$(+ ${i} 1)" value("i")); };

  rewrite { set("$(+ ${n} 2)" value("n")); };

  rewrite { set("$(+ ${n} 1)" value("n_p1")); };
  rewrite { set("$(- ${n} 1)" value("n_m1")); };
  rewrite { set("$(* ${n_p1} ${n_m1})" value("n_pm")); };

  rewrite { set("$(* ${n_pm} ${n})" value("denominator")); };

  rewrite { set("$(* 4 `digits`)" value("nominator")); };
  rewrite { set("$(/ ${nominator} ${denominator})"
                 value("abs_diverge")); };

  rewrite { set("$(* ${direction} ${abs_diverge})"
                value("divereged")); };

  rewrite { set("$(+ ${pi} ${divereged})" value("pi")); };
      
  filter  { "${i}" <= "`iteration`"; };

  destination {
     file("/dev/stdout"
          template("iteration=${i} pi ~ ${pi}\n"));

     unix-stream("/tmp/loop"
                 template("$(format-welf --scope nv-pairs)\n"));
  };
 };
};

pi(iteration(100), digits(1000000000))

Under the hood

The solution has a few limitations because I simplified the task somewhat – I explain these below.

The math

The mathematics is very simple, it uses the Gregory–Leibniz series to approximate π. I chose this formula because it uses only simple operations and does not require functions like sinus. This was important because I planned to use the template functions for the calculation, and the template functions support only basic arithmetics, for example addition, substitution, multiplication, division and so on.

Template functions

For the numerical calculations we use template functions and rewrite rules to reference the template functions I did not optimize anything in the calculations.
A simple fraction can be created as follows:

rewrite {
   set("$(/ ${nominator} ${denominator})"
       value("abs_diverge")
   );
};

The first limitation comes from the template function domain and range. The arithmetic template functions operate on integer numbers, while π is known to be irrational. The approximated version is a rational number, but neither of them can be handled with template functions.

In order to overcome this setback, I decided to transform the domain and range by an arbitrary big number (1000000000).  So we do not really approximate π, but rather 1000000000*π.

The Loop

The above solved the issue of calculating only one iteration, but we clearly need some sort of loop to make it work.

syslog-ng is built to handle logs(events) from its sources. What if I create a destination that posts messages into a file that is also used in a source?

log {
   source  { unix-stream("/tmp/loop"); };
   destination { unix-stream("/tmp/loop"); };
};

This is an infinite loop. In order to start a loop an extra source can be used, the loop starts when we send a message to the extra source.

log {
   source { udp(); };
   source  { unix-stream("/tmp/loop"); };
   destination { unix-stream("/tmp/loop"); };
};

Now this should work, although I would not recommend trying out. 🙂 Also this has an issue that an additional step is required to initiate the loop. I would not like to rely on such things, if possible.

Starting the loop

Luckily it is possible to use the internal source, which sends the internal messages of syslog-ng. We use a new logpath to trigger the loop. The internal source generates multiple events, so to trigger the loop only once, I used a dummy filter to capture only the “syslog-ng starting up” log message. This feels like a hack, because it relies on the message content, and on the internal source, but it works for now.

log {
   source {internal();};
   filter { message("syslog-ng starting up"); };
   destination { unix-stream("/tmp/loop"); };
};

Information between loops

We have a loop and a calculation that could make one iteration. But somehow those calculated data should be shared between the cycles.
The easiest way I could think of is to send the data in the Message itself. The source could parse the message and create the macro values, and then the destination send them to the next iteration of the loop.
The naïve approach would be to customize the message to include the required values, and explicitly set every value:

template("i=${i} pi=${pi}")

This is error prone, and the parser part must follow the exact format. A better option is to use the format-welf template function in the destination, and the kv-parser in the source side. With that, the infinite loop could be resolved with a proper filter.

log {
   source  { stdin(flags(no-parse));
             unix-stream("/tmp/loop" flags(no-parse)); };

   parser  { kv-parser(); };

   rewrite { set("$(+ ${i} 1)" value("i")); };

   filter  { "${i}" <= "100"; };

   destination {
     file("/dev/stdout"
          template("iteration=${i} pi ~ ${pi}\n"));
     unix-stream("/tmp/loop"
                 template("$(format-welf --scope nv-pairs)\n"));
   };
};

This was a good exercise for me, and I got many ideas about what could be achieved within the syslog-ng configuration. If you have a less-than-common use case or configuration for syslog-ng, let us know, we would really like to hear about them.