xhttp_prom: module to generate Prometheus metrics. 1976/head
authorVicente Hernando <vhernando@systemonenoc.com>
Fri, 7 Jun 2019 13:40:55 +0000 (15:40 +0200)
committerVicente Hernando <vhernando@systemonenoc.com>
Fri, 7 Jun 2019 13:40:55 +0000 (15:40 +0200)
src/modules/xhttp_prom/Makefile [new file with mode: 0644]
src/modules/xhttp_prom/doc/Makefile [new file with mode: 0644]
src/modules/xhttp_prom/doc/xhttp_prom.xml [new file with mode: 0644]
src/modules/xhttp_prom/doc/xhttp_prom_admin.xml [new file with mode: 0644]
src/modules/xhttp_prom/prom.c [new file with mode: 0644]
src/modules/xhttp_prom/prom.h [new file with mode: 0644]
src/modules/xhttp_prom/prom_metric.c [new file with mode: 0644]
src/modules/xhttp_prom/prom_metric.h [new file with mode: 0644]
src/modules/xhttp_prom/xhttp_prom.c [new file with mode: 0644]
src/modules/xhttp_prom/xhttp_prom.h [new file with mode: 0644]

diff --git a/src/modules/xhttp_prom/Makefile b/src/modules/xhttp_prom/Makefile
new file mode 100644 (file)
index 0000000..5d7d896
--- /dev/null
@@ -0,0 +1,9 @@
+#
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=xhttp_prom.so
+LIBS=
+
+include ../../Makefile.modules
diff --git a/src/modules/xhttp_prom/doc/Makefile b/src/modules/xhttp_prom/doc/Makefile
new file mode 100644 (file)
index 0000000..eb681bc
--- /dev/null
@@ -0,0 +1,4 @@
+docs = xhttp_prom.xml
+
+docbook_dir = ../../../../doc/docbook
+include $(docbook_dir)/Makefile.module
diff --git a/src/modules/xhttp_prom/doc/xhttp_prom.xml b/src/modules/xhttp_prom/doc/xhttp_prom.xml
new file mode 100644 (file)
index 0000000..5de1395
--- /dev/null
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+       <bookinfo>
+               <title>xHTTP_PROM Module</title>
+               <productname class="trade">&kamailioname;</productname>
+               <authorgroup>
+                       <author>
+                               <firstname>Vicente</firstname>
+                               <surname>Hernando</surname>
+                               <email>vhernando@sonoc.io</email>
+                       </author>
+                       <editor>
+                               <firstname>Vicente</firstname>
+                               <surname>Hernando</surname>
+                               <email>vhernando@sonoc.io</email>
+                       </editor>
+                       <author>
+                           <firstname>Javier</firstname>
+                               <surname>Gallart</surname>
+                               <email>jgallart@sonoc.io</email>
+                       </author>
+               </authorgroup>
+               <copyright>
+                       <year>2019</year>
+                       <holder>www.sonoc.io</holder>
+               </copyright>
+       </bookinfo>
+       <toc></toc>
+
+       <xi:include href="xhttp_prom_admin.xml"/>
+
+</book>
diff --git a/src/modules/xhttp_prom/doc/xhttp_prom_admin.xml b/src/modules/xhttp_prom/doc/xhttp_prom_admin.xml
new file mode 100644 (file)
index 0000000..91ba012
--- /dev/null
@@ -0,0 +1,728 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module User's Guide -->
+
+<chapter>
+
+  <title>&adminguide;</title>
+  
+  <section>
+       <title>Overview</title>
+       <para>
+         This module generates suitable metrics for a Prometheus monitoring platform.
+       </para>
+       <para>
+         It answers Prometheus pull requests (HTTP requests to /metrics URL).
+       </para>
+       <para>
+         The module generates metrics based on &kamailio; statistics, and also the user
+         can create his own metrics (currently counters and gauges).
+       </para>
+       <para>
+         The xHTTP_PROM module uses the xHTTP module to handle HTTP requests.
+         Read the documentation of the xHTTP module for more details.
+       </para>
+       <para>
+         NOTE: This module is based on xHTTP_RPC one.
+       </para>
+       <para>
+         <emphasis>IMPORTANT</emphasis>: This module uses private memory to generate HTTP
+         responses, and shared memory to store all the metrics.
+         Remember to increase size of private and shared memory if you use a huge amount
+         of metrics.
+       </para>
+       <para>
+         Prometheus URLs:
+         <itemizedlist>
+               <listitem>
+                 <ulink url="https://prometheus.io/">https://prometheus.io/</ulink>
+               </listitem>
+               <listitem>
+                 <ulink url="https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels">https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels</ulink>
+               </listitem>
+               <listitem>
+               <ulink url="https://prometheus.io/docs/instrumenting/exposition_formats/">https://prometheus.io/docs/instrumenting/exposition_formats/</ulink>
+               </listitem>
+         </itemizedlist>
+       </para>
+  </section>
+  <section>
+       <title>Dependencies</title>
+       <section>
+         <title>&kamailio; Modules</title>
+         <para>
+               The following modules must be loaded before this module:
+               <itemizedlist>
+                 <listitem>
+                       <para>
+                         <emphasis>xhttp</emphasis> -- xHTTP.
+                       </para>
+                 </listitem>
+               </itemizedlist>
+         </para>
+       </section>
+       <section>
+         <title>External Libraries or Applications</title>
+         <para>
+               The following libraries or applications must be installed before running
+               &kamailio; with this module loaded:
+               <itemizedlist>
+                 <listitem>
+                       <para>
+                         <emphasis>None</emphasis>
+                       </para>
+                 </listitem>
+               </itemizedlist>
+         </para>
+       </section>
+  </section>
+  <section>
+       <title>Parameters</title>
+       <section id="xhttp_prom.p.xhttp_prom_buf_size">
+         <title><varname>xhttp_prom_buf_size</varname> (integer)</title>
+         <para>
+               Specifies the maximum length of the buffer (in bytes) used
+               to write the metric reply information in order to
+               build the HTML response.
+         </para>
+         <para>
+               <emphasis>
+                 Default value is 0 (auto set to 1/3 of the size of the configured pkg mem).
+               </emphasis>
+         </para>
+         <example>
+               <title>Set <varname>xhttp_prom_buf_size</varname> parameter</title>
+               <programlisting format="linespecific">
+...
+modparam("xhttp", "xhttp_prom_buf_size", 1024)
+...
+               </programlisting>
+         </example>
+       </section>
+       <section id="xhttp_prom.p.xhttp_prom_timeout">
+         <title><varname>xhttp_prom_timeout</varname> (integer)</title>
+         <para>
+               Specifies a timeout in minutes. A metric not used during this timeout is
+               automatically deleted. Listing metrics does not count as using them.
+         </para>
+         <para>
+               <emphasis>
+                 Default value is 60 minutes.
+               </emphasis>
+         </para>
+         <example>
+               <title>Set <varname>xhttp_prom_timeout</varname> parameter</title>
+               <programlisting format="linespecific">
+...
+# Set timeout to 10 hours                
+modparam("xhttp", "xhttp_prom_timeout", 600)
+...
+               </programlisting>
+         </example>
+       </section>
+       <section id="xhttp_prom.p.xhttp_prom_stats">
+         <title><varname>xhttp_prom_stats</varname> (str)</title>
+         <para>
+               Specifies which internal statistics from &kamailio; to show.
+               
+               Possible values:
+               <itemizedlist>
+                 <listitem>
+                       <para><emphasis>all</emphasis> - Show whole &kamailio; statistics</para>
+                 </listitem>
+                 <listitem>
+                       <para><emphasis>group_name:</emphasis> - Show all statistics for a group</para>
+                 </listitem>
+                 <listitem>
+                       <para><emphasis>statistic_name</emphasis> - Show a specific statistic.
+                       It automatically finds the group.</para>
+                 </listitem>
+               </itemizedlist>
+         </para>
+         <para>
+               <emphasis>
+                 Default value is "", meaning do not display any &kamailio; statistics.
+               </emphasis>
+         </para>
+         <example>
+               <title>Set <varname>xhttp_prom_stats</varname> parameter</title>
+               <programlisting format="linespecific">
+...
+# show all kamailio statistics.
+modparam("xhttp_prom", "xhttp_prom_stats", "all")
+
+# show statistics for sl group.
+modparam("xhttp_prom", "xhttp_prom_stats", "sl:")
+
+# Show statistic for 200_replies in sl group.
+modparam("xhttp_prom", "xhttp_prom_stats", "200_replies")
+
+# Do not display internal &kamailio; statistics. This is the default option.
+modparam("xhttp_prom", "xhttp_prom_stats", "")
+...
+               </programlisting>
+         </example>
+       </section>
+       <section id="xhttp_prom.p.prom_counter">
+         <title><varname>prom_counter</varname> (str)</title>
+         <para>
+               Create a counter metric.
+         </para>
+         <para>
+               This function declares a counter but the actual counter is only created
+               when using it (by adding to or resetting it)
+         </para>
+         <para>
+               It takes a list of attribute=value separated by semicolon, the attributes can
+               be name and label.
+         </para>
+         <itemizedlist>
+               <listitem>
+                 <para>
+                       <emphasis>name</emphasis> - name of the counter. This attribute is mandatory.
+                       It is used to generate the metric name. Each name is unique, no metric shall
+                       repeat a name.
+                 </para>
+               </listitem>
+               <listitem>
+                 <para>
+                       <emphasis>label</emphasis> - names of labels in the counter. Optional.
+                       Only one label parameter at most allowed in counters.
+                       Each label name is separated by <emphasis>:</emphasis> without spaces.
+                       At most only three label names allowed in each label parameter.
+                       <example><title><varname>prom_counter</varname> label example</title>
+                       <programlisting format="linespecific">
+# Create two labels called method and handler
+label = method:handler
+This would generate  {method="whatever", handler="whatever2"} when building
+the metric.
+                       </programlisting>
+                       </example>
+                 </para>
+               </listitem>
+         </itemizedlist>
+         <example>
+               <title>Set <varname>prom_counter</varname> parameter</title>
+               <programlisting format="linespecific">
+...
+                 
+# Create cnt_first counter with no labels.
+modparam("xhttp_prom", "prom_counter", "name=cnt_first;");
+
+# Create cnt_second counter with no labels.
+modparam("xhttp_prom", "prom_counter", "name=cnt_second;");
+
+
+# Create cnt_third counter with label method
+modparam("xhttp_prom", "prom_counter", "name=cnt_third; label=method");
+
+These lines declare the counter but the actual metric will be created when
+using it by prom_counter_inc or prom_counter_reset functions.
+
+...
+               </programlisting>
+         </example>
+       </section>
+       <section id="xhttp_prom.p.prom_gauge">
+         <title><varname>prom_gauge</varname> (str)</title>
+         <para>
+               Create a gauge metric.
+         </para>
+         <para>
+               This function declares the gauge but the actual gauge is only created
+               when using it (by setting or resetting it)
+         </para>
+         <para>
+               It takes a list of attribute=value separated by semicolon, the attributes can
+               be name and value.
+         </para>
+         <itemizedlist>
+               <listitem>
+                 <para>
+                       <emphasis>name</emphasis> - name of the gauge. This attribute is mandatory.
+                       It is used to generate the metric name. Each name is unique, no metric shall
+                       repeat a name.
+                 </para>
+               </listitem>
+               <listitem>
+                 <para>
+                       <emphasis>label</emphasis> - names of labels in the gauge. Optional.
+                       Only one label parameter at most allowed in gauges.
+                       Each label name is separated by <emphasis>:</emphasis> without spaces.
+                       At most only three label names allowed inside each label parameter.
+                       <example><title><varname>prom_gauge</varname> label example</title>
+                       <programlisting format="linespecific">
+# Create two labels called method and handler
+label = method:handler
+This would generate  {method="whatever", handler="whatever2"} when building
+the metric.
+                       </programlisting>
+                       </example>
+                 </para>
+               </listitem>
+         </itemizedlist>
+         <example>
+               <title>Set <varname>prom_gauge</varname> parameter</title>
+               <programlisting format="linespecific">
+...
+
+# Create gg_first gauge with no labels
+modparam("xhttp_prom", "prom_gauge", "name=gg_first;");
+
+# Create gg_second gauge with no labels
+modparam("xhttp_prom", "prom_gauge", "name=gg_second;");
+
+
+# Create gg_third gauge with two labels method and handler:
+modparam("xhttp_prom", "prom_gauge", "name=gg_third; label=method:handler;");
+
+...
+               </programlisting>
+         </example>
+       </section>
+  </section>
+  <section>
+       <title>Functions</title>
+       <section id="xhttp_prom.f.prom_counter_reset">
+         <title>
+               <function moreinfo="none">prom_counter_reset(name, l0, l1, l2)</function>
+         </title>
+         <para>
+               Get a counter based on its name and labels and reset its value to 0.
+               Name parameter is mandatory. Values of labels are optional (from none up to three).
+               Name in prom_counter_reset has to match same name in prom_counter parameter.
+               Number of labels in prom_counter_reset has to match number of labels in prom_counter parameter.
+               First time a counter is used with this reset function the counter is created if it does not exist already.
+         </para>
+         <para>
+               This function accepts pseudovariables on its parameters.
+         </para>
+         <para>
+               Available via KEMI framework as <emphasis>counter_reset_l0</emphasis>,
+               <emphasis>counter_reset_l1</emphasis>,
+               <emphasis>counter_reset_l2</emphasis> and
+               <emphasis>counter_reset_l3</emphasis>.
+         </para>
+         <example>
+               <title><function>prom_counter_reset</function> usage</title>
+               <programlisting format="linespecific">
+...
+# Definition of counter with prom_counter with labels method and IP
+modparam("xhttp_prom", "prom_counter", "name=cnt01; label=method:IP;");
+...
+# Reset cnt01 counter with two values "push" and "192.168.0.1" in labels to zero.
+# First time we execute this function the counter will be created.
+prom_counter_reset("cnt01", "push", "192.168.0.1");
+...
+# A metric like this will appear when listing this counter:
+kamailio_cnt01 {method="push", IP="192.168.0.1"} 0 1234567890
+...
+               </programlisting>
+         </example>
+       </section>
+       <section id="xhttp_prom.f.prom_gauge_reset">
+         <title>
+               <function moreinfo="none">prom_gauge_reset(name, l0, l1, l2)</function>
+         </title>
+         <para>
+               Get a gauge based on its name and labels and reset its value to 0.
+               Name parameter is mandatory. Values of labels are optional (from none up to three).
+               Name in prom_gauge_reset has to match same name in prom_gauge parameter.
+               Number of labels in prom_gauge_reset has to match number of labels in prom_gauge parameter.
+               First time a gauge is used with this reset function the gauge is created if it does not exist already.
+         </para>
+         <para>
+               This function accepts pseudovariables on its parameters.
+         </para>
+         <para>
+               Available via KEMI framework as <emphasis>gauge_reset_l0</emphasis>,
+               <emphasis>gauge_reset_l1</emphasis>,
+               <emphasis>gauge_reset_l2</emphasis> and
+               <emphasis>gauge_reset_l3</emphasis>.
+         </para>
+         <example>
+               <title><function>prom_gauge_reset</function> usage</title>
+               <programlisting format="linespecific">
+...
+# Definition of gauge with prom_gauge with labels method and IP
+modparam("xhttp_prom", "prom_gauge", "name=cnt01; label=method:IP;");
+...
+# Reset cnt01 gauge with two values "push" and "192.168.0.1" in labels to zero.
+# First time we execute this function the gauge will be created.
+prom_gauge_reset("cnt01", "push", "192.168.0.1");
+...
+# A metric like this will appear when listing this gauge:
+kamailio_cnt01 {method="push", IP="192.168.0.1"} 0 1234567890
+...
+               </programlisting>
+         </example>
+       </section>
+       <section id="xhttp_prom.f.prom_counter_inc">
+         <title>
+               <function moreinfo="none">prom_counter_inc(name, number, l0, l1, l2)</function>
+         </title>
+         <para>
+               Get a counter identified by its name and labels and increase its value by a number.
+               If counter does not exist it creates the counter, initializes it to zero and adds the number.
+         </para>
+         <para>
+               Name is mandatory, number is mandatory.
+               Number has to be positive or zero (integer).
+               l0, l1, l2 are values of labels and are optional.
+         </para>
+         <para>
+               name value and number of labels have to match a previous counter definition with prom_counter.
+         </para>
+         <para>
+               This function accepts pseudovariables on its parameters.
+         </para>
+         <para>
+               Available via KEMI framework as <emphasis>counter_inc_l0</emphasis>,
+               <emphasis>counter_inc_l1</emphasis>,
+               <emphasis>counter_inc_l2</emphasis> and
+               <emphasis>counter_inc_l3</emphasis>.
+         </para>
+         <example>
+               <title><function>prom_counter_inc</function> usage</title>
+               <programlisting format="linespecific">
+...
+# Definition of cnt01 counter with no labels.
+modparam("xhttp_prom", "prom_counter", "name=cnt01;");
+...
+# Add 10 to value of cnt01 counter (with no labels) If counter does not exist it gets created.
+prom_counter_inc("cnt01", "10");
+...
+
+# Definition of cnt02 counter with two labels method and IP
+modparam("xhttp_prom", "prom_counter", "name=cnt02; label=method:IP;");
+...
+# Add 15 to value of cnt02 counter with labels method and IP. It creates the counter if it does not exist.
+prom_counter_inc("cnt02", "15", "push", "192.168.0.1");
+# When listed the metric it will show a line like this:
+kamailio_cnt02 {method="push", IP="192.168.0.1"} 15 1234567890
+...
+               </programlisting>
+         </example>
+       </section>
+       <section id="xhttp_prom.f.prom_gauge_set">
+         <title>
+               <function moreinfo="none">prom_gauge_set(name, number, l0, l1, l2)</function>
+         </title>
+         <para>
+               Get a gauge identified by its name and labels and set its value to a number.
+               If gauge does not exist it creates the gauge and assigns the value to it.
+         </para>
+         <para>
+               Name is mandatory, number is mandatory.
+               Number is a string that will be parsed as a float.
+               l0, l1, l2 are values of labels and are optional.
+         </para>
+         <para>
+               name value and number of labels have to match a previous gauge definition with prom_gauge.
+         </para>
+         <para>
+               This function accepts pseudovariables on its parameters.
+         </para>
+         <para>
+               Available via KEMI framework as <emphasis>gauge_set_l0</emphasis>,
+               <emphasis>gauge_set_l1</emphasis>,
+               <emphasis>gauge_set_l2</emphasis> and
+               <emphasis>gauge_set_l3</emphasis>.
+         </para>
+         <example>
+               <title><function>prom_gauge_set</function> usage</title>
+               <programlisting format="linespecific">
+...
+# Definition of gg01 gauge with no labels.
+modparam("xhttp_prom", "prom_gauge", "name=gg01;");
+...
+# Assign -12.5 to value of gg01 gauge (with no labels) If gauge does not exist it gets created
+prom_gauge_set("gg01", "-12.5");
+...
+
+# Definition of gg02 gauge with two labels method and IP
+modparam("xhttp_prom", "prom_gauge", "name=cnt02; label=method:IP;");
+...
+# Assign 2.8 to value of gg02 gauge with labels method and IP. It creates the gauge if it does not exist.
+prom_gauge_set("gg02", "2.8", "push", "192.168.0.1");
+# When listed the metric it will show a line like this:
+kamailio_gg02 {method="push", IP="192.168.0.1"} 2.8 1234567890
+...
+               </programlisting>
+         </example>
+       </section>
+       <section id="xhttp_prom.f.prom_dispatch">
+         <title>
+               <function moreinfo="none">prom_dispatch()</function>
+         </title>
+         <para>
+               Handle the HTTP request and generate a response.
+         </para>
+         <para>
+               Available via KEMI framework as <emphasis>xhttp_prom.dispatch</emphasis>
+         </para>
+         <example>
+               <title><function>prom_dispatch</function> usage</title>
+               <programlisting format="linespecific">
+...
+# Needed to use SIP frames as HTTP ones.
+tcp_accept_no_cl=yes
+...
+# xhttp module depends on sl one.
+loadmodule "sl.so"
+loadmodule "xhttp.so"
+loadmodule "xhttp_prom.so"
+...
+# show all kamailio statistics.
+modparam("xhttp_prom", "xhttp_prom_stats", "all")
+...
+event_route[xhttp:request] {
+       $var(xhttp_prom_root) = $(hu{s.substr,0,8});
+       if ($var(xhttp_prom_root) == "/metrics")
+               prom_dispatch();
+       else
+               xhttp_reply("200", "OK", "text/html",
+                       "&lt;html&gt;&lt;body&gt;Wrong URL $hu&lt;/body&gt;&lt;/html&gt;");
+}
+...
+               </programlisting>
+         </example>
+         <example>
+               <title><function>prom_dispatch</function> usage (more complete)</title>
+               <para>Another example with counters and gauge:</para>
+               <programlisting format="linespecific">
+...
+# Needed to use SIP frames as HTTP ones.
+tcp_accept_no_cl=yes
+
+# xhttp module depends on sl one.
+loadmodule "sl.so"
+loadmodule "xhttp.so"
+loadmodule "xhttp_prom.so"
+
+# show &kamailio; statistics for sl group
+modparam("xhttp_prom", "xhttp_prom_stats", "sl:")
+
+# Define two counters and a gauge
+modparam("xhttp_prom", "prom_counter", "name=cnt_first;");
+modparam("xhttp_prom", "prom_counter", "name=cnt_second; label=method:IP");
+modparam("xhttp_prom", "prom_gauge", "name=gg_first; label=handler");
+
+event_route[xhttp:request] {
+       $var(xhttp_prom_root) = $(hu{s.substr,0,8});
+       if ($var(xhttp_prom_root) == "/metrics") {
+           prom_counter_reset("cnt_first");
+               prom_counter_inc("cnt_second", "10", "push", "192.168.0.1");
+               prom_gauge_set("gg_first", "5.2", "my_handler");
+               prom_dispatch();
+       } else
+               xhttp_reply("200", "OK", "text/html",
+                       "&lt;html&gt;&lt;body&gt;Wrong URL $hu&lt;/body&gt;&lt;/html&gt;");
+}
+...
+
+We can manually check the result with a web browser:
+We assume &kamailio; runs in localhost and port is set to default (same as SIP: 5060)
+http://localhost:5060
+...
+
+# User defined metrics
+kamailio_cnt_first 0 1554839325427
+kamailio_cnt_second {method="push", IP="192.168.0.1"} 10 1554839325427
+kamailio_gg_first{handler="my_handler"} 5.2 1554839325427
+
+# Kamailio internal statistics
+kamailio_sl_1xx_replies 0 1554839325427
+kamailio_sl_200_replies 15 1554839325427
+kamailio_sl_202_replies 0 1554839325427
+kamailio_sl_2xx_replies 0 1554839325427
+kamailio_sl_300_replies 0 1554839325427
+kamailio_sl_301_replies 0 1554839325427
+kamailio_sl_302_replies 0 1554839325427
+kamailio_sl_3xx_replies 0 1554839325427
+kamailio_sl_400_replies 0 1554839325427
+kamailio_sl_401_replies 0 1554839325427
+kamailio_sl_403_replies 0 1554839325427
+kamailio_sl_404_replies 0 1554839325427
+kamailio_sl_407_replies 0 1554839325427
+kamailio_sl_408_replies 0 1554839325427
+kamailio_sl_483_replies 0 1554839325427
+kamailio_sl_4xx_replies 0 1554839325427
+kamailio_sl_500_replies 0 1554839325427
+kamailio_sl_5xx_replies 0 1554839325427
+kamailio_sl_6xx_replies 0 1554839325427
+kamailio_sl_failures 0 1554839325427
+kamailio_sl_received_ACKs 0 1554839325427
+kamailio_sl_sent_err_replies 0 1554839325427
+kamailio_sl_sent_replies 15 1554839325427
+kamailio_sl_xxx_replies 0 1554839325461
+...
+               </programlisting>
+         </example>
+       </section>
+       <section id="xhttp_prom.f.prom_check_uri">
+         <title>
+               <function moreinfo="none">prom_check_uri()</function>
+         </title>
+         <para>
+               Check if path of HTTP URL equals /metrics. This avoids us to check hu variable
+               from xHTTP module.
+         </para>
+         <para>NOTE: Remember not to block /metrics URL in xHTTP module</para>
+         <para>
+               Available via KEMI framework as <emphasis>xhttp_prom.check_uri</emphasis>
+         </para>
+         <example>
+               <title><function>prom_check_uri</function> usage</title>
+               <programlisting format="linespecific">
+...
+# Needed to use SIP frames as HTTP ones.
+tcp_accept_no_cl=yes
+...
+# xhttp module depends on sl one.
+loadmodule "sl.so"
+loadmodule "xhttp.so"
+loadmodule "xhttp_prom.so"
+...
+# show all kamailio statistics.
+modparam("xhttp_prom", "xhttp_prom_stats", "all")
+...
+event_route[xhttp:request] {
+       if (prom_check_uri())
+               prom_dispatch();
+       else
+               xhttp_reply("200", "OK", "text/html",
+                       "&lt;html&gt;&lt;body&gt;Wrong URL $hu&lt;/body&gt;&lt;/html&gt;");
+}
+...
+               </programlisting>
+         </example>
+       </section>
+  </section>
+  <section>
+       <title><acronym>RPC</acronym> Commands</title>
+       <section  id="xhttp_prom.rpc.counter_reset">
+         <title><function moreinfo="none">xhttp_prom.counter_reset</function></title>
+         <para>
+               Set a counter to zero.
+         </para>
+      <para>
+        Name: <emphasis>xhttp_prom.counter_reset</emphasis>
+      </para>
+      <para>Parameters:</para>
+         <itemizedlist>
+               <listitem><para><emphasis>name</emphasis>: name of the counter (mandatory)</para></listitem>
+               <listitem><para><emphasis>l0</emphasis>: value of the first label (optional)</para></listitem>
+               <listitem><para><emphasis>l1</emphasis>: value of second label (optional)</para></listitem>
+               <listitem><para><emphasis>l2</emphasis>: value of the third label (optional)</para></listitem>
+         </itemizedlist>
+         <example>
+               <title><function>xhttp_prom.counter_reset</function> usage</title>
+               <programlisting format="linespecific">
+                 ...
+                 &kamcmd; xhttp_prom.counter_reset "cnt01" "push" "192.168.0.1"
+                 ...
+               </programlisting>
+         </example>
+    </section>
+       <section  id="xhttp_prom.rpc.counter_inc">
+         <title><function moreinfo="none">xhttp_prom.counter_inc</function></title>
+         <para>
+               Add a number to a counter based on its name and labels.
+         </para>
+      <para>
+        Name: <emphasis>xhttp_prom.counter_inc</emphasis>
+      </para>
+      <para>Parameters:</para>
+         <itemizedlist>
+               <listitem><para><emphasis>name</emphasis>: name of the counter (mandatory)</para></listitem>
+               <listitem><para><emphasis>number</emphasis>: integer to add to counter value. Negative values not allowed.</para></listitem>
+               <listitem><para><emphasis>l0</emphasis>: value of the first label (optional)</para></listitem>
+               <listitem><para><emphasis>l1</emphasis>: value of second label (optional)</para></listitem>
+               <listitem><para><emphasis>l2</emphasis>: value of the third label (optional)</para></listitem>
+         </itemizedlist>
+         <example>
+               <title><function>xhttp_prom.counter_inc</function> usage</title>
+               <programlisting format="linespecific">
+                 ...
+                 &kamcmd; xhttp_prom.counter_inc "cnt01" 15 "push" "192.168.0.1"
+                 ...
+               </programlisting>
+         </example>
+    </section>
+       <section  id="xhttp_prom.rpc.gauge_reset">
+         <title><function moreinfo="none">xhttp_prom.gauge_reset</function></title>
+         <para>
+               Set gauge value to zero. Select gauge based on its name and labels.
+         </para>
+      <para>
+        Name: <emphasis>xhttp_prom.gauge_reset</emphasis>
+      </para>
+      <para>Parameters:</para>
+         <itemizedlist>
+               <listitem><para><emphasis>name</emphasis>: name of the gauge (mandatory)</para></listitem>
+               <listitem><para><emphasis>l0</emphasis>: value of the first label (optional)</para></listitem>
+               <listitem><para><emphasis>l1</emphasis>: value of second label (optional)</para></listitem>
+               <listitem><para><emphasis>l2</emphasis>: value of the third label (optional)</para></listitem>
+         </itemizedlist>
+         <example>
+               <title><function>xhttp_prom.gauge_reset</function> usage</title>
+               <programlisting format="linespecific">
+                 ...
+                 &kamcmd; xhttp_prom.gauge_reset "gg01" "push" "192.168.0.1"
+                 ...
+               </programlisting>
+         </example>
+    </section>
+       <section  id="xhttp_prom.rpc.gauge_set">
+         <title><function moreinfo="none">xhttp_prom.gauge_set</function></title>
+         <para>
+               Set a gauge to a number. Select the gauge by its name and labels.
+         </para>
+      <para>
+        Name: <emphasis>xhttp_prom.gauge_set</emphasis>
+      </para>
+      <para>Parameters:</para>
+         <itemizedlist>
+                               <listitem><para><emphasis>name</emphasis>: name of the gauge (mandatory)</para></listitem>
+               <listitem><para><emphasis>number</emphasis>: float value to set the gauge to (mandatory)</para></listitem>
+               <listitem><para><emphasis>l0</emphasis>: value of the first label (optional)</para></listitem>
+               <listitem><para><emphasis>l1</emphasis>: value of second label (optional)</para></listitem>
+               <listitem><para><emphasis>l2</emphasis>: value of the third label (optional)</para></listitem>
+         </itemizedlist>
+         <example>
+               <title><function>xhttp_prom.gauge_set</function> usage</title>
+               <programlisting format="linespecific">
+                 ...
+                 &kamcmd; xhttp_prom.gauge_set "gg01" -- -5.2
+                 ...
+               </programlisting>
+         </example>
+    </section>
+       <section  id="xhttp_prom.rpc.metric_list_print">
+         <title><function moreinfo="none">xhttp_prom.metric_list_print</function></title>
+         <para>
+               List of all user defined metrics.
+         </para>
+      <para>
+        Name: <emphasis>xhttp_prom.metric_list_print</emphasis>
+      </para>
+      <para>Parameters:<emphasis>none</emphasis></para>
+         <example>
+               <title><function>xhttp_prom.metric_list_print</function> usage</title>
+               <programlisting format="linespecific">
+                 ...
+                 &kamcmd; xhttp_prom.metric_list_print
+                 ...
+               </programlisting>
+         </example>
+       </section>
+  </section>
+</chapter>     
diff --git a/src/modules/xhttp_prom/prom.c b/src/modules/xhttp_prom/prom.c
new file mode 100644 (file)
index 0000000..835eb54
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2012 VoIP Embedded, Inc.
+ *
+ * Copyright (C) 2019 Vicente Hernando (Sonoc)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+/**
+ * Functionality of prometheus module.
+ */
+
+#include <string.h>
+#include <time.h>
+#include <inttypes.h>
+#include <stdarg.h>
+
+#include "../../core/counters.h"
+#include "../../core/ut.h"
+
+#include "prom.h"
+#include "prom_metric.h"
+
+/**
+ * Delete current buffer data.
+ */
+void prom_body_delete(prom_ctx_t *ctx)
+{
+       ctx->reply.body.len = 0;
+}
+
+/**
+ * Write some data in prom_body buffer.
+ *
+ * /return number of bytes written.
+ * /return -1 on error.
+ */
+int prom_body_printf(prom_ctx_t *ctx, char *fmt, ...)
+{
+       struct xhttp_prom_reply *reply = &ctx->reply;
+       
+       va_list ap;
+       
+       va_start(ap, fmt);
+
+       LM_DBG("Body current length: %d\n", reply->body.len);
+
+       char *p = reply->buf.s + reply->body.len;
+       int remaining_len = reply->buf.len - reply->body.len;
+       LM_DBG("Remaining length: %d\n", remaining_len);
+
+       /* int vsnprintf(char *str, size_t size, const char *format, va_list ap); */
+       int len = vsnprintf(p, remaining_len, fmt, ap);
+       if (len < 0) {
+               LM_ERR("Error printing body buffer\n");
+               goto error;
+       } else if (len >= remaining_len) {
+               LM_ERR("Error body buffer overflow: %d (%d)\n", len, remaining_len);
+               goto error;
+       } else {
+               /* Buffer printed OK. */
+               reply->body.len += len;
+               LM_DBG("Body new length: %d\n", reply->body.len);
+       }
+
+       va_end(ap);
+       return len;
+
+error:
+       va_end(ap);
+       return -1;
+}
+
+/**
+ * Get current timestamp in milliseconds.
+ *
+ * /param ts pointer to timestamp integer.
+ * /return 0 on success.
+ */
+int get_timestamp(uint64_t *ts)
+{
+       assert(ts);
+       
+       struct timeval current_time;
+       if (gettimeofday(&current_time, NULL) < 0) {
+               LM_ERR("failed to get current time!\n");
+               return -1;
+       }
+
+       *ts = (uint64_t)current_time.tv_sec*1000 +
+               (uint64_t)current_time.tv_usec/1000;
+       
+       return 0;
+}
+
+/**
+ * Generate a string suitable for a Prometheus metric.
+ *
+ * /return 0 on success.
+ */
+static int metric_generate(prom_ctx_t *ctx, str *group, str *name, counter_handle_t *h)
+{
+       long counter_val = counter_get_val(*h);
+
+       /* Calculate timestamp. */
+       uint64_t ts;
+       if (get_timestamp(&ts)) {
+               LM_ERR("Error getting current timestamp\n");
+               return -1;
+       }
+       LM_DBG("Timestamp: %" PRIu64 "\n", ts);
+
+       
+       /* LM_DBG("%.*s:%.*s = %lu\n",
+          group->len, group->s, name->len, name->s, counter_val); */
+       LM_DBG("kamailio_%.*s_%.*s %lu %" PRIu64 "\n",
+                  group->len, group->s, name->len, name->s,
+                  counter_val, (uint64_t)ts);
+
+       if (prom_body_printf(ctx, "kamailio_%.*s_%.*s %lu %" PRIu64 "\n",
+                                                group->len, group->s, name->len, name->s,
+                                                counter_val, (uint64_t)ts) == -1) {
+               LM_ERR("Fail to print\n");
+               return -1;
+       }
+
+       return 0;
+}
+
+/**
+ * Statistic getter callback.
+ */
+static void prom_get_grp_vars_cbk(void* p, str* g, str* n, counter_handle_t h)
+{
+       metric_generate(p, g, n, &h);
+}
+
+/**
+ * Group statistic getter callback.
+ */
+static void prom_get_all_grps_cbk(void* p, str* g)
+{
+       counter_iterate_grp_vars(g->s, prom_get_grp_vars_cbk, p);
+}
+
+#define STATS_MAX_LEN 1024
+
+/**
+ * Get statistics (based on stats_get_all)
+ *
+ * /return 0 on success
+ */
+int prom_stats_get(prom_ctx_t *ctx, str *stat)
+{
+       if (stat == NULL) {
+               LM_ERR("No stats set\n");
+               return -1;
+       }
+
+       prom_body_delete(ctx);
+       
+       LM_DBG("User defined statistics\n");
+       if (prom_metric_list_print(ctx)) {
+               LM_ERR("Fail to print user defined metrics\n");
+               return -1;
+       }
+
+       LM_DBG("Statistics for: %.*s\n", stat->len, stat->s);
+
+       int len = stat->len;
+
+       stat_var *s_stat;
+
+       if (len == 0) {
+               LM_DBG("Do not show Kamailio statistics\n");
+
+       }
+       else if (len==3 && strncmp("all", stat->s, 3)==0) {
+               LM_DBG("Showing all statistics\n");
+               if (prom_body_printf(
+                               ctx, "\n# Kamailio whole internal statistics\n") == -1) {
+                       LM_ERR("Fail to print\n");
+                       return -1;
+               }       
+
+               counter_iterate_grp_names(prom_get_all_grps_cbk, ctx);
+       }
+       else if (stat->s[len-1]==':') {
+               LM_DBG("Showing statistics for group: %.*s\n", stat->len, stat->s);
+
+               if (len == 1) {
+                       LM_ERR("Void group for statistics: %.*s\n", stat->len, stat->s);
+                       return -1;
+                       
+               } else {
+                       if (prom_body_printf(
+                                       ctx, "\n# Kamailio statistics for group: %.*s\n",
+                                       stat->len, stat->s) == -1) {
+                               LM_ERR("Fail to print\n");
+                               return -1;
+                       }
+
+                       /* Temporary stat_tmp string. */
+                       char stat_tmp[STATS_MAX_LEN];
+                       memcpy(stat_tmp, stat->s, len);
+                       stat_tmp[len-1] = '\0';
+                       counter_iterate_grp_vars(stat_tmp, prom_get_grp_vars_cbk, ctx);
+                       stat_tmp[len-1] = ':';
+               }
+       }
+       else {
+               LM_DBG("Showing statistic for: %.*s\n", stat->len, stat->s);
+
+               s_stat = get_stat(stat);
+               if (s_stat) {
+                       str group_str, name_str;
+                       group_str.s = get_stat_module(s_stat);
+                       if (group_str.s) {
+                               group_str.len = strlen(group_str.s);
+                       } else {
+                               group_str.len = 0;
+                       }
+                       
+                       name_str.s = get_stat_name(s_stat);
+                       if (name_str.s) {
+                               name_str.len = strlen(name_str.s);
+                       } else {
+                               name_str.len = 0;
+                       }
+                       
+                       LM_DBG("%s:%s = %lu\n",
+                                  ZSW(get_stat_module(s_stat)), ZSW(get_stat_name(s_stat)),
+                                  get_stat_val(s_stat));
+
+                       if (group_str.len && name_str.len && s_stat) {
+                               if (prom_body_printf(
+                                               ctx, "\n# Kamailio statistics for: %.*s\n",
+                                               stat->len, stat->s) == -1) {
+                                       LM_ERR("Fail to print\n");
+                                       return -1;
+                               }
+
+                               counter_handle_t stat_handle;
+                               stat_handle.id = (unsigned short)(unsigned long)s_stat;
+                               if (metric_generate(ctx, &group_str, &name_str, &stat_handle)) {
+                                       LM_ERR("Failed to generate metric: %.*s - %.*s\n",
+                                                  group_str.len, group_str.s,
+                                                  name_str.len, name_str.s);
+                                       return -1;
+                               }
+                       } else {
+                               LM_ERR("Not enough length for group (%d) or name (%d)\n",
+                                          group_str.len, name_str.len);
+                               return -1;
+                       }
+               } /* if s_stat */
+               else {
+                       LM_ERR("stat not found: %.*s\n", stat->len, stat->s);
+                       return -1;
+               }
+       } /* if len == 0 */
+
+       return 0;
+} /* prom_stats_get */
+
diff --git a/src/modules/xhttp_prom/prom.h b/src/modules/xhttp_prom/prom.h
new file mode 100644 (file)
index 0000000..c41db5d
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 VoIP Embedded, Inc.
+ *
+ * Copyright (C) 2019 Vicente Hernando (Sonoc)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+/**
+ * Header for functionality of prometheus module.
+ */
+
+#ifndef _PROM_H_
+#define _PROM_H_
+
+#include "xhttp_prom.h"
+
+/**
+ * Get current timestamp in milliseconds.
+ *
+ * /param ts pointer to timestamp integer.
+ * /return 0 on success.
+ */
+int get_timestamp(uint64_t *ts);
+
+/**
+ * Write some data in prom_body buffer.
+ *
+ * /return number of bytes written.
+ * /return -1 on error.
+ */
+int prom_body_printf(prom_ctx_t *ctx, char *fmt, ...);
+
+/**
+ * Get statistics (based on stats_get_all)
+ *
+ * /return 0 on success
+ */
+int prom_stats_get(prom_ctx_t *ctx, str *stat);
+
+#endif // _PROM_H_
diff --git a/src/modules/xhttp_prom/prom_metric.c b/src/modules/xhttp_prom/prom_metric.c
new file mode 100644 (file)
index 0000000..89c904a
--- /dev/null
@@ -0,0 +1,1361 @@
+/*
+ * Copyright (C) 2012 VoIP Embedded, Inc.
+ *
+ * Copyright (C) 2019 Vicente Hernando (Sonoc)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include "../../core/mem/shm_mem.h"
+#include "../../core/locking.h"
+#include "../../core/ut.h"
+#include "../../core/parser/parse_param.h"
+
+#include "prom_metric.h"
+#include "prom.h"
+
+/* TODO: Every internal function locks and unlocks the metric system. */
+
+typedef enum metric_type {
+       M_UNSET = 0,
+       M_COUNTER = 1,
+       M_GAUGE = 2
+       /* TODO: Add more types. */
+} metric_type_t;
+
+/**
+ * Struct to store a string (node of a list)
+ */
+typedef struct prom_lb_node_s {
+       str n;
+       struct prom_lb_node_s *next;
+} prom_lb_node_t;
+
+/**
+ * Struct to store a list of strings (labels)
+ */
+typedef struct prom_lb_s {
+       int n_elem; /* Number of strings. */
+       struct prom_lb_node_s *lb;
+       /* TODO: Hashes? */
+} prom_lb_t;
+
+/**
+ * Struct to store a value of a label.
+ */
+typedef struct prom_lvalue_s {
+       prom_lb_t lval;
+       uint64_t ts; /* timespan. Last time metric was modified. */
+       union {
+               uint64_t cval;
+               double gval;
+       } m;
+       struct prom_lvalue_s *next;
+} prom_lvalue_t;
+
+/**
+ * Struct to store a metric.
+ */
+typedef struct prom_metric_s {
+       metric_type_t type;
+       str name;
+       struct prom_lb_s *lb_name; /* Names of labels. */
+       struct prom_lvalue_s *lval_list;
+       struct prom_metric_s *next;
+} prom_metric_t;
+
+/**
+ * Data related to Prometheus metrics.
+ */
+static prom_metric_t *prom_metric_list = NULL;
+static gen_lock_t *prom_lock = NULL; /* Lock to protect Prometheus metrics. */
+static uint64_t lvalue_timeout = 120000; /* Timeout in milliseconds for old lvalue struct. */
+
+static void prom_counter_free(prom_metric_t *m_cnt);
+static void prom_gauge_free(prom_metric_t *m_gg);
+static void prom_metric_free(prom_metric_t *metric);
+static void prom_lb_free(prom_lb_t *prom_lb, int shared_mem);
+static void prom_lb_node_free(prom_lb_node_t *lb_node, int shared_mem);
+static int prom_lb_node_add(prom_lb_t *m_lb, char *s, int len, int shared_mem);
+static void prom_lvalue_free(prom_lvalue_t *plv);
+static void prom_lvalue_list_free(prom_lvalue_t *plv);
+
+/**
+ * Free list of Prometheus metrics.
+ */
+static void prom_metric_list_free()
+{
+       prom_metric_t *p, *next;
+
+       p = prom_metric_list;
+       while (p) {
+               next = p->next;
+               prom_metric_free(p);
+               p = next;
+       }
+
+       prom_metric_list = NULL;
+}
+
+/**
+ * Initialize user defined metrics.
+ */
+int prom_metric_init(int timeout_minutes)
+{
+       /* Initialize timeout. minutes to milliseconds. */
+       if (timeout_minutes < 1) {
+               LM_ERR("Invalid timeout: %d\n", timeout_minutes);
+               return -1;
+       }
+       lvalue_timeout = ((uint64_t)timeout_minutes) * 60000;
+       LM_DBG("lvalue_timeout set to %" PRIu64 "\n", lvalue_timeout);
+       
+       /* Initialize lock. */
+       prom_lock = lock_alloc();
+       if (!prom_lock) {
+               LM_ERR("Cannot allocate lock\n");
+               return -1;
+       }
+
+       if (lock_init(prom_lock) == NULL) {
+               LM_ERR("Cannot initialize the lock\n");
+               lock_dealloc(prom_lock);
+               prom_lock = NULL;
+               return -1;
+       }
+
+       /* Everything went fine. */
+       return 0;
+}
+
+/**
+ * Close user defined metrics.
+ */
+void prom_metric_close()
+{
+       /* Free lock */
+       if (prom_lock) {
+               LM_DBG("Freeing lock\n");
+               lock_destroy(prom_lock);
+               lock_dealloc(prom_lock);
+               prom_lock = NULL;
+       }
+
+       /* Free metric list. */
+       if (prom_metric_list) {
+               LM_DBG("Freeing list of Prometheus metrics\n");
+               prom_metric_list_free();
+       }
+}
+
+/**
+ * Free a metric.
+ */
+static void prom_metric_free(prom_metric_t *metric)
+{
+       assert(metric);
+
+       if (metric->type == M_COUNTER) {
+               prom_counter_free(metric);
+       } else if (metric->type == M_GAUGE) {
+               prom_gauge_free(metric);
+       } else {
+               LM_ERR("Unknown metric: %d\n", metric->type);
+               return;
+       }
+}
+
+/**
+ * Free a counter.
+ */
+static void prom_counter_free(prom_metric_t *m_cnt)
+{
+       assert(m_cnt);
+
+       assert(m_cnt->type == M_COUNTER);
+
+       if (m_cnt->name.s) {
+               shm_free(m_cnt->name.s);
+       }
+
+       prom_lb_free(m_cnt->lb_name, 1);
+
+       prom_lvalue_list_free(m_cnt->lval_list);
+       
+       shm_free(m_cnt);
+}
+
+/**
+ * Get a metric based on its name.
+ *
+ * /return pointer to metric on success.
+ * /return NULL on error.
+ */
+static prom_metric_t* prom_metric_get(str *s_name)
+{
+       prom_metric_t *p = prom_metric_list;
+
+       while (p) {
+               if (s_name->len == p->name.len && strncmp(s_name->s, p->name.s, s_name->len) == 0) {
+                       LM_DBG("Metric found: %.*s\n", p->name.len, p->name.s);
+                       break;
+               }
+               p = p->next;
+       }
+
+       return p;
+}
+
+/**
+ * Compare prom_lb_t structure using some strings.
+ *
+ * /return 0 if prom_lb_t matches the strings.
+ */
+static int prom_lb_compare(prom_lb_t *plb, str *l1, str *l2, str *l3)
+{
+       if (plb == NULL) {
+               if (l1 != NULL) {
+                       return -1;
+               }
+               return 0;
+       }
+
+       if (l1 == NULL) {
+               if (plb->n_elem != 0) {
+                       return -1;
+               }
+               return 0;
+       }
+
+       /* At least one label. */
+       prom_lb_node_t *p = plb->lb;    
+       if (p == NULL) {
+               return -1;
+       }
+       if (l1->len != p->n.len || strncmp(l1->s, p->n.s, l1->len)) {
+               return -1;
+       }
+
+       p = p->next;
+       if (l2 == NULL) {
+               if (plb->n_elem != 1) {
+                       return -1;
+               }
+               return 0;
+       }
+
+       /* At least two labels. */
+       if (p == NULL) {
+               return -1;
+       }
+       if (l2->len != p->n.len || strncmp(l2->s, p->n.s, l2->len)) {
+               return -1;
+       }
+
+       p = p->next;
+       if (l3 == NULL) {
+               if (plb->n_elem != 2) {
+                       return -1;
+               }
+               return 0;
+       }
+
+       /* At least three labels. */
+       if (p == NULL) {
+               return -1;
+       }
+       if (l3->len != p->n.len || strncmp(l3->s, p->n.s, l3->len)) {
+               return -1;
+       }
+
+       return 0; 
+}
+
+/**
+ * Compare two lval structures.
+ *
+ * /return 0 if they are the same.
+ */
+static int prom_lvalue_compare(prom_lvalue_t *p, str *l1, str *l2, str *l3)
+{
+       if (p == NULL) {
+               LM_ERR("No lvalue structure\n");
+               return -1;
+       }
+
+       if (prom_lb_compare(&p->lval, l1, l2, l3)) {
+               return -1;
+       }
+
+       /* Comparison matches. */
+       return 0;
+}
+
+/**
+ * Free an lvalue structure.
+ * Only defined for shared memory.
+ */
+static void prom_lvalue_free(prom_lvalue_t *plv)
+{
+       if (plv == NULL) {
+               return;
+       }
+
+       /* Free list of strings. */
+       prom_lb_node_t *lb_node = plv->lval.lb;
+       while (lb_node) {
+               prom_lb_node_t *next = lb_node->next;
+               prom_lb_node_free(lb_node, 1);
+               lb_node = next;
+       }
+       
+       shm_free(plv);
+}
+
+/**
+ * Free a list of lvalue structures.
+ * Only defined for shared memory.
+ */
+static void prom_lvalue_list_free(prom_lvalue_t *plv)
+{
+       while (plv) {
+               prom_lvalue_t *next = plv->next;
+               prom_lvalue_free(plv);
+               plv = next;
+       }
+}
+
+/**
+ * Fill lvalue data in prom_lvalue_t structure based on three strings.
+ * Only defined for shared memory.
+ *
+ * /return 0 on success.
+ */
+static int prom_lvalue_lb_create(prom_lvalue_t *lv, str *l1, str *l2, str *l3)
+{
+       if (lv == NULL) {
+               LM_ERR("No lvalue structure\n");
+               return -1;
+       }
+
+       /* Initialize lv->lval */
+       prom_lb_t *p = &lv->lval;
+       p->n_elem = 0;
+       p->lb = NULL;
+
+       if (l1 == NULL) {
+               /* No labels. */
+               return 0;
+       }
+
+       /* At least 1 label. */
+       if (prom_lb_node_add(p, l1->s, l1->len, 1)) {
+               LM_ERR("Cannot add label string\n");
+               return -1;
+       }
+       
+       if (l2 == NULL) {
+               return 0;
+       }
+
+       /* At least 2 labels. */
+       if (prom_lb_node_add(p, l2->s, l2->len, 1)) {
+               LM_ERR("Cannot add label string\n");
+               return -1;
+       }
+
+       if (l3 == NULL) {
+               return 0;
+       }
+
+       /* 3 labels. */
+       if (prom_lb_node_add(p, l3->s, l3->len, 1)) {
+               LM_ERR("Cannot add label string\n");
+               return -1;
+       }
+
+       return 0;
+}
+
+/**
+ * Create and insert a lvalue structure into a metric.
+ * It only works in shared memory.
+ *
+ * /return pointer to newly created structure on success.
+ * /return NULL on error.
+ */
+static prom_lvalue_t* prom_metric_lvalue_create(prom_metric_t *p_m, str *l1, str *l2, str *l3)
+{
+       if (p_m == NULL) {
+               LM_ERR("No metric found\n");
+               return NULL;
+       }
+
+       prom_lvalue_t *plv = NULL;
+       plv = (prom_lvalue_t*)shm_malloc(sizeof(*plv));
+       if (plv == NULL) {
+               LM_ERR("shm out of memory\n");
+               return NULL;
+       }
+       memset(plv, 0, sizeof(*plv));
+
+       if (prom_lvalue_lb_create(plv, l1, l2, l3)) {
+               LM_ERR("Cannot create list of strings\n");
+               goto error;
+       }
+
+       /* Place plv at the end of lvalue list. */
+       prom_lvalue_t **l = &p_m->lval_list;
+       while (*l != NULL) {
+               l = &((*l)->next);
+       }
+       *l = plv;
+       plv->next = NULL;
+
+       /* Everything went fine. */
+       return plv;
+
+error:
+       prom_lvalue_free(plv);
+       return NULL;
+}
+
+/**
+ * Find a lvalue based on its labels.
+ * If it does not exist it creates a new one and inserts it into the metric.
+ *
+ * /return pointer to lvalue on success.
+ * /return NULL on error.
+ */
+static prom_lvalue_t* prom_lvalue_get_create(prom_metric_t *p_m, str *l1, str *l2, str *l3)
+{
+       if (!p_m) {
+               LM_ERR("No metric found\n");
+               return NULL;
+       }
+
+       /* Check number of elements in labels. */
+       if (l1 == NULL) {
+               if (p_m->lb_name != NULL) {
+                       LM_ERR("Number of labels does not match for metric: %.*s\n",
+                                  p_m->name.len, p_m->name.s);
+                       return NULL;
+               }
+
+       } else if (l2 == NULL) {
+               if (!p_m || !p_m->lb_name || p_m->lb_name->n_elem != 1) {
+                       LM_ERR("Number of labels does not match for metric: %.*s\n",
+                                  p_m->name.len, p_m->name.s);
+                       return NULL;
+               }
+
+       } else if (l3 == NULL) {
+               if (!p_m || !p_m->lb_name || p_m->lb_name->n_elem != 2) {
+                       LM_ERR("Number of labels does not match for metric: %.*s\n",
+                                  p_m->name.len, p_m->name.s);
+                       return NULL;
+               }
+
+       } else {
+               if (!p_m || !p_m->lb_name || p_m->lb_name->n_elem != 3) {
+                       LM_ERR("Number of labels does not match for metric: %.*s\n",
+                                  p_m->name.len, p_m->name.s);
+                       return NULL;
+               }
+
+       } /* if l1 == NULL */
+
+       /* Find existing prom_lvalue_t structure. */
+       prom_lvalue_t *p = p_m->lval_list;
+       while (p) {
+               if (prom_lvalue_compare(p, l1, l2, l3) == 0) {
+                       LM_DBG("LValue structure found\n");
+                       return p;
+               }
+               p = p->next;
+       }
+
+       LM_DBG("Creating lvalue %.*s\n", p_m->name.len, p_m->name.s);
+       /* No lvalue structure found. Create and insert a new one. */
+       p = prom_metric_lvalue_create(p_m, l1, l2, l3);
+       if (p == NULL) {
+               LM_ERR("Cannot create a new lvalue structure\n");
+               return NULL;
+       }
+
+       return p;
+}
+
+/**
+ * Delete old lvalue structures in a metric.
+ * Only for shared memory.
+ */
+static void prom_metric_timeout_delete(prom_metric_t *p_m)
+{
+       if (p_m == NULL) {
+               return;
+       }
+
+       /* Get timestamp. */
+       uint64_t ts;
+       if (get_timestamp(&ts)) {
+               LM_ERR("Fail to get timestamp\n");
+               return;
+       }
+
+       /* Parse lvalue list deleting outdated items. */
+       prom_lvalue_t **l = &p_m->lval_list;
+       while (*l != NULL) {
+               prom_lvalue_t *current = *l;
+               
+               if (ts - current->ts > lvalue_timeout) {
+                       LM_DBG("Timeout found\n");
+                       *l = (*l)->next;
+
+                       /* Free current lvalue. */
+                       prom_lvalue_free(current);
+
+               } else {
+                       l = &((*l)->next);
+               }
+               
+       } /* while *l != NULL */
+}
+
+/**
+ * Delete old lvalue structures in list of metrics.
+ */
+static void    prom_metric_list_timeout_delete()
+{
+       prom_metric_t *p = prom_metric_list;
+
+       while (p) {
+               prom_metric_timeout_delete(p);
+               p = p->next;
+       }
+}
+
+/**
+ * Get a lvalue based on its metric name and labels.
+ * If metric name exists but no lvalue matches it creates a new lvalue.
+ *
+ * /return NULL if no lvalue was found or created.
+ * /return pointer to lvalue on success.
+ */
+static prom_lvalue_t* prom_metric_lvalue_get(str *s_name, metric_type_t m_type,
+                                                                                        str *l1, str *l2, str *l3)
+{
+       if (!s_name || s_name->len == 0 || s_name->s == NULL) {
+               LM_ERR("No name for metric\n");
+               return NULL;
+       }
+
+       /* Delete old lvalue structures. */
+       prom_metric_list_timeout_delete();
+       
+    prom_metric_t *p_m = prom_metric_get(s_name);
+       if (p_m == NULL) {
+               LM_ERR("No metric found for name: %.*s\n", s_name->len, s_name->s);
+               return NULL;
+       }
+
+       if (p_m->type != m_type) {
+               LM_ERR("Metric type does not match for metric: %.*s\n", s_name->len, s_name->s);
+               return NULL;
+       }
+
+       /* Get timestamp. */
+       uint64_t ts;
+       if (get_timestamp(&ts)) {
+               LM_ERR("Fail to get timestamp\n");
+               return NULL;
+       }
+
+       prom_lvalue_t *p_lv = NULL;
+       p_lv = prom_lvalue_get_create(p_m, l1, l2, l3);
+       if (p_lv == NULL) {
+               LM_ERR("Failed to create lvalue\n");
+               return NULL;
+       }
+
+       p_lv->ts = ts;
+       LM_DBG("New timestamp: %" PRIu64 "\n", p_lv->ts);
+       
+       return p_lv;
+}
+
+/**
+ * Free a node in a list of strings.
+ */
+static void prom_lb_node_free(prom_lb_node_t *lb_node, int shared_mem)
+{
+       if (lb_node == NULL) {
+               return;
+       }
+
+       /* Free the str. */
+       if (shared_mem) {
+               if (lb_node->n.s) {
+                       shm_free(lb_node->n.s);
+               }
+       } else {
+               if (lb_node->n.s) {
+                       pkg_free(lb_node->n.s);
+               }       
+       }
+       
+       if (shared_mem) {
+               shm_free(lb_node);
+       } else {
+               pkg_free(lb_node);
+       }
+
+}
+
+/**
+ * Free a list of str (for labels).
+ *
+ * /param shared_mem 0 means pkg memory otherwise shared one.
+ */
+static void prom_lb_free(prom_lb_t *prom_lb, int shared_mem)
+{
+       if (prom_lb == NULL) {
+               return;
+       }
+
+       /* Free nodes. */
+       prom_lb_node_t *lb_node = prom_lb->lb;
+       while (lb_node) {
+               prom_lb_node_t *next = lb_node->next;
+               prom_lb_node_free(lb_node, shared_mem);
+               lb_node = next;
+       }
+
+       /* Free prom_bl_t object. */
+       if (shared_mem) {
+               shm_free(prom_lb);
+       } else {
+               pkg_free(prom_lb);
+       }
+}
+
+#define LABEL_SEP ':'  /* Field separator for labels. */
+
+/**
+ * Add a string to list of strings.
+ *
+ * /param m_lb pointer to list of strings.
+ * /param s whole string.
+ * /param pos_start position of first character to add.
+ * /param pos_end position after last character to add.
+ *
+ * /return 0 on success.
+ */
+static int prom_lb_node_add(prom_lb_t *m_lb, char *s, int len, int shared_mem)
+{
+       if (len <= 0) {
+               LM_ERR("Void string to add\n");
+               return -1;
+       }
+       
+       LM_DBG("Label: adding (%.*s)\n", len, s);
+       prom_lb_node_t *lb_node;
+       if (shared_mem) {
+               /* Shared memory */
+               lb_node = (prom_lb_node_t*)shm_malloc(sizeof(*lb_node));
+               if (lb_node == NULL) {
+                       LM_ERR("shm out of memory\n");
+                       goto error;
+               }
+               memset(lb_node, 0, sizeof(*lb_node));
+
+       } else {
+               /* Pkg memory */
+               lb_node = (prom_lb_node_t*)pkg_malloc(sizeof(*lb_node));
+               if (lb_node == NULL) {
+                       LM_ERR("pkg out of memory\n");
+                       goto error;
+               }
+               memset(lb_node, 0, sizeof(*lb_node));
+
+       } /* if shared_mem */
+
+       /* Allocate space for str. */
+       if (shared_mem) {
+               /* Shared memory */
+               /* We left space for zero at the end. */
+               lb_node->n.s = (char*)shm_malloc(len + 1);
+               if (lb_node->n.s == NULL) {
+                       LM_ERR("shm out of memory\n");
+                       goto error;
+               }
+               memcpy(lb_node->n.s, s, len);
+               lb_node->n.len = len;
+               
+       } else {
+               /* Pkg memory */
+               /* We left space for zero at the end. */
+               lb_node->n.s = (char*)shm_malloc(len + 1);
+               if (lb_node->n.s == NULL) {
+                       LM_ERR("shm out of memory\n");
+                       goto error;
+               }
+               memcpy(lb_node->n.s, s, len);
+               lb_node->n.len = len;
+
+       } /* if shared_mem */
+
+       LM_DBG("Node str: %.*s\n", lb_node->n.len, lb_node->n.s);
+       
+       /* Place node at the end of string list. */
+       prom_lb_node_t **l = &m_lb->lb;
+       while (*l != NULL) {
+               l = &((*l)->next);
+       }
+       *l = lb_node;
+       lb_node->next = NULL;
+
+       m_lb->n_elem++;
+       
+       /* Everything went fine. */
+       return 0;
+
+error:
+       prom_lb_node_free(lb_node, shared_mem);
+       return -1;
+}
+
+/**
+ * Create a list of str (for labels)
+ *
+ * /param shared_mem 0 means pkg memory otherwise shared one.
+ *
+ * /return pointer to prom_lb_t struct on success.
+ * /return NULL on error.
+ */
+static prom_lb_t* prom_lb_create(str *lb_str, int shared_mem)
+{
+       prom_lb_t *m_lb = NULL;
+
+       if (!lb_str || lb_str->len == 0 || lb_str->s == NULL) {
+               LM_ERR("No label string\n");
+               goto error;
+       }
+       
+       if (shared_mem) {
+               /* Shared memory */
+               m_lb = (prom_lb_t*)shm_malloc(sizeof(*m_lb));
+               if (m_lb == NULL) {
+                       LM_ERR("shm out of memory\n");
+                       goto error;
+               }
+               memset(m_lb, 0, sizeof(*m_lb));
+
+       } else {
+               /* Pkg memory */
+               m_lb = (prom_lb_t*)pkg_malloc(sizeof(*m_lb));
+               if (m_lb == NULL) {
+                       LM_ERR("pkg out of memory\n");
+                       goto error;
+               }
+               memset(m_lb, 0, sizeof(*m_lb));
+
+       } /* if shared_mem */
+
+       /* Add strings to m_lb */
+       int len = lb_str->len;
+       char *s = lb_str->s;
+       int pos_end = 0, pos_start = 0;
+       while (pos_end < len) {
+               if (s[pos_end] == LABEL_SEP) {
+                       if (prom_lb_node_add(m_lb, s + pos_start, pos_end - pos_start, shared_mem)) {
+                               LM_ERR("Cannot add label string\n");
+                               goto error;
+                       }
+                       pos_start = pos_end + 1;
+               }
+
+               pos_end++;
+       }
+       /* Add last string if it does exist. */
+       if (pos_end > pos_start) {
+               if (prom_lb_node_add(m_lb, s + pos_start, pos_end - pos_start, shared_mem)) {
+                       LM_ERR("Cannot add label string\n");
+                       goto error;
+               }
+       }
+       
+       /* Everything fine */
+       return m_lb;
+
+error:
+       prom_lb_free(m_lb, shared_mem);
+       return NULL;
+}
+
+/**
+ * Create a label and add it to a metric.
+ *
+ * /return 0 on success.
+ */
+static int prom_label_create(prom_metric_t *mt, str *lb_str)
+{
+       if (mt == NULL) {
+               LM_ERR("No metric available\n");
+               return -1;
+       }
+       
+       if (lb_str == NULL || lb_str->len == 0 || lb_str->s == NULL) {
+               LM_ERR("No label available\n");
+               return -1;
+       }
+
+       if (mt->lb_name != NULL) {
+               LM_ERR("Label already created\n");
+               return -1;
+       }
+
+       /* Create new label name in shared memory */
+       prom_lb_t *new_lb;
+       new_lb = prom_lb_create(lb_str, 1);
+       if (new_lb == NULL) {
+               LM_ERR("Cannot create label: %.*s\n", lb_str->len, lb_str->s);
+               return -1;
+       }
+
+       /* Add label name to metric. */
+       mt->lb_name = new_lb;
+
+       /* Everything went fine. */
+       return 0;
+}
+
+/**
+ * Create a counter and add it to list.
+ */
+int prom_counter_create(char *spec)
+{
+       param_t *pit=NULL;
+       param_hooks_t phooks;
+       prom_metric_t *m_cnt = NULL;
+       str s;
+
+       s.s = spec;
+       s.len = strlen(spec);
+       if(s.s[s.len-1]==';')
+               s.len--;
+       if (parse_params(&s, CLASS_ANY, &phooks, &pit)<0)
+       {
+               LM_ERR("failed parsing params value\n");
+               goto error;
+       }
+       m_cnt = (prom_metric_t*)shm_malloc(sizeof(prom_metric_t));
+       if (m_cnt == NULL) {
+               LM_ERR("shm out of memory\n");
+               goto error;
+       }
+       memset(m_cnt, 0, sizeof(*m_cnt));
+       m_cnt->type = M_COUNTER;
+       
+       param_t *p = NULL;
+       for (p = pit; p; p = p->next) {
+               if (p->name.len == 5 && strncmp(p->name.s, "label", 5) == 0) {
+                       /* Fill counter label. */
+                       if (prom_label_create(m_cnt, &p->body)) {
+                               LM_ERR("Error creating label: %.*s\n", p->body.len, p->body.s);
+                               goto error;
+                       }
+                       LM_DBG("label = %.*s\n", p->body.len, p->body.s);
+
+               } else if (p->name.len == 4 && strncmp(p->name.s, "name", 4) == 0) {
+                       /* Fill counter name. */
+                       if (shm_str_dup(&m_cnt->name, &p->body)) {
+                               LM_ERR("Error creating counter name: %.*s\n", p->body.len, p->body.s);
+                               goto error;
+                       }
+                       LM_DBG("name = %.*s\n", m_cnt->name.len, m_cnt->name.s);
+
+               } else {
+                       LM_ERR("Unknown field: %.*s (%.*s)\n", p->name.len, p->name.s,
+                                  p->body.len, p->body.s);
+                       goto error;
+               }
+       } /* for p = pit */
+
+       if (m_cnt->name.s == NULL || m_cnt->name.len == 0) {
+               LM_ERR("No counter name\n");
+               goto error;
+       }
+
+       /* Place counter at the end of list. */
+       prom_metric_t **l = &prom_metric_list;
+       while (*l != NULL) {
+               l = &((*l)->next);
+       }
+       *l = m_cnt;
+       m_cnt->next = NULL;
+
+       /* Everything went fine. */
+       return 0;
+
+error:
+       if (pit != NULL) {
+               free_params(pit);
+       }
+       if (m_cnt != NULL) {
+               prom_counter_free(m_cnt);
+       }
+       return -1;
+}
+
+/**
+ * Free a gauge.
+ */
+static void prom_gauge_free(prom_metric_t *m_gg)
+{
+       assert(m_gg);
+
+       assert(m_gg->type == M_GAUGE);
+
+       if (m_gg->name.s) {
+               shm_free(m_gg->name.s);
+       }
+
+       prom_lb_free(m_gg->lb_name, 1);
+
+       prom_lvalue_list_free(m_gg->lval_list);
+       
+       shm_free(m_gg);
+}
+
+/**
+ * Create a gauge and add it to list.
+ */
+int prom_gauge_create(char *spec)
+{
+       param_t *pit=NULL;
+       param_hooks_t phooks;
+       prom_metric_t *m_gg = NULL;
+       str s;
+
+       s.s = spec;
+       s.len = strlen(spec);
+       if(s.s[s.len-1]==';')
+               s.len--;
+       if (parse_params(&s, CLASS_ANY, &phooks, &pit)<0)
+       {
+               LM_ERR("failed parsing params value\n");
+               goto error;
+       }
+       m_gg = (prom_metric_t*)shm_malloc(sizeof(prom_metric_t));
+       if (m_gg == NULL) {
+               LM_ERR("shm out of memory\n");
+               goto error;
+       }
+       memset(m_gg, 0, sizeof(*m_gg));
+       m_gg->type = M_GAUGE;
+       
+       param_t *p = NULL;
+       for (p = pit; p; p = p->next) {
+               if (p->name.len == 5 && strncmp(p->name.s, "label", 5) == 0) {
+                       /* Fill gauge label. */
+                       if (prom_label_create(m_gg, &p->body)) {
+                               LM_ERR("Error creating label: %.*s\n", p->body.len, p->body.s);
+                               goto error;
+                       }
+                       LM_DBG("label = %.*s\n", p->body.len, p->body.s);
+
+               } else if (p->name.len == 4 && strncmp(p->name.s, "name", 4) == 0) {
+                       /* Fill gauge name. */
+                       if (shm_str_dup(&m_gg->name, &p->body)) {
+                               LM_ERR("Error creating gauge name: %.*s\n", p->body.len, p->body.s);
+                               goto error;
+                       }
+                       LM_DBG("name = %.*s\n", m_gg->name.len, m_gg->name.s);
+
+               } else {
+                       LM_ERR("Unknown field: %.*s (%.*s)\n", p->name.len, p->name.s,
+                                  p->body.len, p->body.s);
+                       goto error;
+               }
+       } /* for p = pit */
+
+       if (m_gg->name.s == NULL || m_gg->name.len == 0) {
+               LM_ERR("No gauge name\n");
+               goto error;
+       }
+
+       /* Place gauge at the end of list. */
+       prom_metric_t **l = &prom_metric_list;
+       while (*l != NULL) {
+               l = &((*l)->next);
+       }
+       *l = m_gg;
+       m_gg->next = NULL;
+
+       /* Everything went fine. */
+       return 0;
+
+error:
+       if (pit != NULL) {
+               free_params(pit);
+       }
+       if (m_gg != NULL) {
+               prom_gauge_free(m_gg);
+       }
+       return -1;
+}
+
+/**
+ * Add some positive amount to a counter.
+ */
+int prom_counter_inc(str *s_name, int number, str *l1, str *l2, str *l3)
+{
+       lock_get(prom_lock);
+
+       /* Find a lvalue based on its metric name and labels. */
+       prom_lvalue_t *p = NULL;
+       p = prom_metric_lvalue_get(s_name, M_COUNTER, l1, l2, l3);
+       if (!p) {
+               LM_ERR("Cannot find counter: %.*s\n", s_name->len, s_name->s);
+               lock_release(prom_lock);
+               return -1;
+       }
+
+       /* Add to counter value. */
+       p->m.cval += number;
+       
+       lock_release(prom_lock);
+       return 0;
+}
+
+/**
+ * Reset a counter.
+ */
+int prom_counter_reset(str *s_name, str *l1, str *l2, str *l3)
+{
+       lock_get(prom_lock);
+
+       /* Find a lvalue based on its metric name and labels. */
+       prom_lvalue_t *p = NULL;
+       p = prom_metric_lvalue_get(s_name, M_COUNTER, l1, l2, l3);
+       if (!p) {
+               LM_ERR("Cannot find counter: %.*s\n", s_name->len, s_name->s);
+               lock_release(prom_lock);
+               return -1;
+       }
+
+       /* Reset counter value. */
+       p->m.cval = 0;
+       
+       lock_release(prom_lock);
+       return 0;
+}
+
+/**
+ * Set a value in a gauge.
+ */
+int prom_gauge_set(str *s_name, double number, str *l1, str *l2, str *l3)
+{
+       lock_get(prom_lock);
+
+       /* Find a lvalue based on its metric name and labels. */
+       prom_lvalue_t *p = NULL;
+       p = prom_metric_lvalue_get(s_name, M_GAUGE, l1, l2, l3);
+       if (!p) {
+               LM_ERR("Cannot find gauge: %.*s\n", s_name->len, s_name->s);
+               lock_release(prom_lock);
+               return -1;
+       }
+
+       /* Set gauge value. */
+       p->m.gval = number;
+               
+       lock_release(prom_lock);
+       return 0;
+}
+
+/**
+ * Reset value in a gauge.
+ */
+int prom_gauge_reset(str *s_name, str *l1, str *l2, str *l3)
+{
+       lock_get(prom_lock);
+
+       /* Find a lvalue based on its metric name and labels. */
+       prom_lvalue_t *p = NULL;
+       p = prom_metric_lvalue_get(s_name, M_GAUGE, l1, l2, l3);
+       if (!p) {
+               LM_ERR("Cannot find gauge: %.*s\n", s_name->len, s_name->s);
+               lock_release(prom_lock);
+               return -1;
+       }
+
+       /* Reset counter value. */
+       p->m.gval = 0.0;
+               
+       lock_release(prom_lock);
+       return 0;
+}
+
+/**
+ * Print labels.
+ *
+ * /return 0 on success.
+ */
+static int prom_label_print(prom_ctx_t *ctx, prom_lb_t *lb_name, prom_lb_t *plval)
+{
+       if (!ctx) {
+               LM_ERR("No context\n");
+               goto error;
+       }
+
+       if (!plval) {
+               goto error;
+       }
+
+       if (plval->n_elem == 0) {
+               /* Nothing to print. */
+               return 0;
+       }       
+       
+       if (!lb_name || lb_name->n_elem == 0) {
+               /* Nothing to print. */
+               return 0;
+       }
+
+       prom_lb_node_t *lb_name_node = lb_name->lb;
+       prom_lb_node_t *plval_node = plval->lb;
+       while (lb_name_node && plval_node) {
+               if (lb_name_node == lb_name->lb) {
+                       /* First label */
+                       if (prom_body_printf(ctx,
+                                                                "{"
+                                       ) == -1) {
+                               LM_ERR("Fail to print\n");
+                               goto error;
+                       }
+               } else {
+                       /* Not the first label */
+                       if (prom_body_printf(ctx,
+                                                                ", "
+                                       ) == -1) {
+                               LM_ERR("Fail to print\n");
+                               goto error;
+                       }
+               }
+               
+               if (prom_body_printf(ctx,
+                                                        "%.*s=\"%.*s\"",
+                                                        lb_name_node->n.len, lb_name_node->n.s,
+                                                        plval_node->n.len, plval_node->n.s
+                               ) == -1) {
+                       LM_ERR("Fail to print\n");
+                       goto error;
+               }
+
+               lb_name_node = lb_name_node->next;
+               plval_node = plval_node->next;
+       } /* while (lb_name_node && plval_node) */
+
+       /* Close labels. */
+       if (prom_body_printf(ctx,
+                                                "}"
+                       ) == -1) {
+               LM_ERR("Fail to print\n");
+               goto error;
+       }
+
+       /* Everything fine. */
+       return 0;
+       
+error:
+
+       return -1;
+}
+
+/**
+ * Print a user defined metric lvalue pair.
+ *
+ * /return 0 on success.
+ */
+static int prom_metric_lvalue_print(prom_ctx_t *ctx, prom_metric_t *p, prom_lvalue_t *pvl)
+{
+       if (!ctx) {
+               LM_ERR("No context\n");
+               goto error;
+       }
+
+       if (!p) {
+               LM_ERR("No metric\n");
+               goto error;
+       }
+
+       if (!pvl) {
+               LM_ERR("No lvalue structure\n");
+               goto error;
+       }
+       
+       if (p->type == M_COUNTER) {
+
+               uint64_t ts;
+               if (get_timestamp(&ts)) {
+                       LM_ERR("Fail to get timestamp\n");
+                       goto error;
+               }
+               LM_DBG("Counter kamailio_%.*s %" PRIu64 "\n",
+                          p->name.len, p->name.s,
+                          ts
+                       );
+               if (prom_body_printf(ctx,
+                                                        "kamailio_%.*s",
+                                                        p->name.len, p->name.s
+                               ) == -1) {
+                       LM_ERR("Fail to print\n");
+                       goto error;
+               }
+
+               /* Print labels */
+               if (prom_label_print(ctx, p->lb_name, &pvl->lval)) {
+                       LM_ERR("Fail to print labels\n");
+                       goto error;
+               }
+
+               if (prom_body_printf(ctx,
+                                                        " %" PRIu64,
+                                                        pvl->m.cval
+                               ) == -1) {
+                       LM_ERR("Fail to print\n");
+                       goto error;
+               }
+
+               if (prom_body_printf(ctx,
+                                                        " %" PRIu64 "\n",
+                                                        ts
+                               ) == -1) {
+                       LM_ERR("Fail to print\n");
+                       goto error;
+               }
+
+       } else if (p->type == M_GAUGE) {
+               uint64_t ts;
+               if (get_timestamp(&ts)) {
+                       LM_ERR("Fail to get timestamp\n");
+                       goto error;
+               }
+               LM_DBG("Gauge kamailio_%.*s %" PRId64 "\n",
+                          p->name.len, p->name.s,
+                          ts
+                       );
+               
+               if (prom_body_printf(ctx,
+                                                        "kamailio_%.*s",
+                                                        p->name.len, p->name.s
+                               ) == -1) {
+                       LM_ERR("Fail to print\n");
+                       goto error;
+               }
+               
+               /* Print labels */
+               if (prom_label_print(ctx, p->lb_name, &pvl->lval)) {
+                       LM_ERR("Fail to print labels\n");
+                       goto error;
+               }
+
+               if (prom_body_printf(ctx,
+                                                        " %f",
+                                                        pvl->m.gval
+                               ) == -1) {
+                       LM_ERR("Fail to print\n");
+                       goto error;
+               }
+
+               if (prom_body_printf(ctx,
+                                                        " %" PRIu64 "\n",
+                                                        ts
+                               ) == -1) {
+                       LM_ERR("Fail to print\n");
+                       goto error;
+               }
+               
+       } else {
+               LM_DBG("Unknown metric type: %d\n", p->type);
+       }
+
+       /* Everything went fine. */
+       return 0;
+
+error:
+       return -1;
+}
+
+/**
+ * Print user defined metrics.
+ *
+ * /return 0 on success.
+ */
+int prom_metric_list_print(prom_ctx_t *ctx)
+{
+       lock_get(prom_lock);
+       
+       prom_metric_t *p = prom_metric_list;
+       if (p) {
+               if (prom_body_printf(
+                               ctx, "# User defined metrics\n") == -1) {
+                       LM_ERR("Fail to print\n");
+                       goto error;
+               }
+       } else {
+               if (prom_body_printf(
+                               ctx, "# NO User defined metrics\n") == -1) {
+                       LM_ERR("Fail to print\n");
+                       goto error;
+               }
+       } /* if p */
+
+       while (p) {
+
+               prom_lvalue_t *pvl = p->lval_list;
+               
+               while (pvl) {
+                       if (prom_metric_lvalue_print(ctx, p, pvl)) {
+                               LM_ERR("Failed to print\n");
+                               goto error;
+                       }
+                       
+                       pvl = pvl->next;
+                       
+               } /* while pvl */
+
+               p = p->next;
+               
+       } /* while p */
+
+       lock_release(prom_lock);
+       return 0;
+
+error:
+       lock_release(prom_lock);
+       return -1;
+}
+
diff --git a/src/modules/xhttp_prom/prom_metric.h b/src/modules/xhttp_prom/prom_metric.h
new file mode 100644 (file)
index 0000000..91b667b
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2012 VoIP Embedded, Inc.
+ *
+ * Copyright (C) 2019 Vicente Hernando (Sonoc)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+/**
+ * Header user defined metrics for Prometheus.
+ */
+
+#ifndef _PROM_METRIC_H_
+#define _PROM_METRIC_H_
+
+#include "xhttp_prom.h"
+
+/**
+ * Initialize user defined metrics.
+ */
+int prom_metric_init(int timeout_minutes);
+
+/**
+ * Close user defined metrics.
+ */
+void prom_metric_close();
+
+/**
+ * Create a counter and add it to list.
+ */
+int prom_counter_create(char *spec);
+
+/**
+ * Create a gauge and add it to list.
+ */
+int prom_gauge_create(char *spec);
+
+/**
+ * Print user defined metrics.
+ */
+int prom_metric_list_print(prom_ctx_t *ctx);
+
+/**
+ * Reset a counter.
+ */
+int prom_counter_reset(str *s_name, str *l1, str *l2, str *l3);
+
+/**
+ * Reset value in a gauge.
+ */
+int prom_gauge_reset(str *s_name, str *l1, str *l2, str *l3);
+
+/**
+ * Add some positive amount to a counter.
+ */
+int prom_counter_inc(str *s_name, int number, str *l1, str *l2, str *l3);
+
+/**
+ * Set a value in a gauge.
+ */
+int prom_gauge_set(str *s_name, double number, str *l1, str *l2, str *l3);
+
+#endif // _PROM_METRIC_H_
diff --git a/src/modules/xhttp_prom/xhttp_prom.c b/src/modules/xhttp_prom/xhttp_prom.c
new file mode 100644 (file)
index 0000000..13c8920
--- /dev/null
@@ -0,0 +1,2092 @@
+/*
+ * Copyright (C) 2012 VoIP Embedded, Inc.
+ *
+ * Copyright (C) 2019 Vicente Hernando (Sonoc)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../../core/sr_module.h"
+#include "../../core/str.h"
+#include "../../modules/xhttp/api.h"
+#include "../../core/nonsip_hooks.h"
+#include "../../core/mod_fix.h"
+#include "../../core/kemi.h"
+#include "../../core/rpc.h"
+#include "../../core/rpc_lookup.h"
+
+#include "xhttp_prom.h"
+#include "prom.h"
+#include "prom_metric.h"
+
+/** @addtogroup xhttp_prom
+ * @ingroup modules
+ * @{
+ *
+ * <h1>Overview of Operation</h1>
+ * This module provides a web interface for Prometheus server.
+ * It is built on top of the xhttp API module.
+ */
+
+/** @file
+ *
+ * This is the main file of xhttp_prom module which contains all the functions
+ * related to http processing, as well as the module interface.
+ */
+
+MODULE_VERSION
+
+/* Declaration of static functions. */
+
+str XHTTP_PROM_REASON_OK = str_init("OK");
+str XHTTP_PROM_CONTENT_TYPE_TEXT_HTML = str_init("text/plain; version=0.0.4");
+
+static rpc_export_t rpc_cmds[];
+static int mod_init(void);
+static void mod_destroy(void);
+static int w_prom_check_uri(sip_msg_t* msg);
+static int w_prom_dispatch(sip_msg_t* msg);
+static int w_prom_counter_reset_l0(struct sip_msg* msg, char* pname);
+static int w_prom_counter_reset_l1(struct sip_msg* msg, char* pname, char *l1);
+static int w_prom_counter_reset_l2(struct sip_msg* msg, char* pname, char *l1, char *l2);
+static int w_prom_counter_reset_l3(struct sip_msg* msg, char* pname, char *l1, char *l2, char *l3);
+static int w_prom_gauge_reset_l0(struct sip_msg* msg, char* pname);
+static int w_prom_gauge_reset_l1(struct sip_msg* msg, char* pname, char *l1);
+static int w_prom_gauge_reset_l2(struct sip_msg* msg, char* pname, char *l1, char *l2);
+static int w_prom_gauge_reset_l3(struct sip_msg* msg, char* pname, char *l1, char *l2, char *l3);
+static int w_prom_counter_inc_l0(struct sip_msg* msg, char *pname, char* pnumber);
+static int w_prom_counter_inc_l1(struct sip_msg* msg, char *pname, char* pnumber, char *l1);
+static int w_prom_counter_inc_l2(struct sip_msg* msg, char *pname, char* pnumber, char *l1, char *l2);
+static int w_prom_counter_inc_l3(struct sip_msg* msg, char *pname, char* pnumber, char *l1, char *l2, char *l3);
+static int w_prom_gauge_set_l0(struct sip_msg* msg, char *pname, char* pnumber);
+static int w_prom_gauge_set_l1(struct sip_msg* msg, char *pname, char* pnumber, char *l1);
+static int w_prom_gauge_set_l2(struct sip_msg* msg, char *pname, char* pnumber, char *l1, char *l2);
+static int w_prom_gauge_set_l3(struct sip_msg* msg, char *pname, char* pnumber, char *l1, char *l2, char *l3);
+static int fixup_metric_reset(void** param, int param_no);
+static int fixup_counter_inc(void** param, int param_no);
+
+int prom_counter_param(modparam_t type, void *val);
+int prom_gauge_param(modparam_t type, void *val);
+
+/** The context of the xhttp_prom request being processed.
+ *
+ * This is a global variable that records the context of the xhttp_prom request
+ * being currently processed.
+ * @sa prom_ctx
+ */
+static prom_ctx_t ctx;
+
+static xhttp_api_t xhttp_api;
+
+/* It does not show Kamailio statistics by default. */
+str xhttp_prom_stats = str_init("");
+
+int buf_size = 0;
+int timeout_minutes = 0;
+char error_buf[ERROR_REASON_BUF_LEN];
+
+/* module commands */
+static cmd_export_t cmds[] = {
+       {"prom_check_uri",(cmd_function)w_prom_check_uri,0,0,0,
+        REQUEST_ROUTE|EVENT_ROUTE},
+       {"prom_dispatch",(cmd_function)w_prom_dispatch,0,0,0,
+        REQUEST_ROUTE|EVENT_ROUTE},
+       {"prom_counter_reset", (cmd_function)w_prom_counter_reset_l0, 1, fixup_metric_reset,
+        0, ANY_ROUTE},
+       {"prom_counter_reset", (cmd_function)w_prom_counter_reset_l1, 2, fixup_metric_reset,
+        0, ANY_ROUTE},
+       {"prom_counter_reset", (cmd_function)w_prom_counter_reset_l2, 3, fixup_metric_reset,
+        0, ANY_ROUTE},
+       {"prom_counter_reset", (cmd_function)w_prom_counter_reset_l3, 4, fixup_metric_reset,
+        0, ANY_ROUTE},
+       {"prom_gauge_reset", (cmd_function)w_prom_gauge_reset_l0, 1, fixup_metric_reset,
+        0, ANY_ROUTE},
+       {"prom_gauge_reset", (cmd_function)w_prom_gauge_reset_l1, 2, fixup_metric_reset,
+        0, ANY_ROUTE},
+       {"prom_gauge_reset", (cmd_function)w_prom_gauge_reset_l2, 3, fixup_metric_reset,
+        0, ANY_ROUTE},
+       {"prom_gauge_reset", (cmd_function)w_prom_gauge_reset_l3, 4, fixup_metric_reset,
+        0, ANY_ROUTE},
+       {"prom_counter_inc", (cmd_function)w_prom_counter_inc_l0, 2, fixup_counter_inc,
+        0, ANY_ROUTE},
+       {"prom_counter_inc", (cmd_function)w_prom_counter_inc_l1, 3, fixup_counter_inc,
+        0, ANY_ROUTE},
+       {"prom_counter_inc", (cmd_function)w_prom_counter_inc_l2, 4, fixup_counter_inc,
+        0, ANY_ROUTE},
+       {"prom_counter_inc", (cmd_function)w_prom_counter_inc_l3, 5, fixup_counter_inc,
+        0, ANY_ROUTE},
+       {"prom_gauge_set", (cmd_function)w_prom_gauge_set_l0, 2, fixup_metric_reset,
+        0, ANY_ROUTE},
+       {"prom_gauge_set", (cmd_function)w_prom_gauge_set_l1, 3, fixup_metric_reset,
+        0, ANY_ROUTE},
+       {"prom_gauge_set", (cmd_function)w_prom_gauge_set_l2, 4, fixup_metric_reset,
+        0, ANY_ROUTE},
+       {"prom_gauge_set", (cmd_function)w_prom_gauge_set_l3, 5, fixup_metric_reset,
+        0, ANY_ROUTE},
+       { 0, 0, 0, 0, 0, 0}
+};
+
+static param_export_t params[]={
+       {"xhttp_prom_buf_size", INT_PARAM,      &buf_size},
+       {"xhttp_prom_stats",    PARAM_STR,      &xhttp_prom_stats},
+       {"prom_counter",        PARAM_STRING|USE_FUNC_PARAM, (void*)prom_counter_param},
+       {"prom_gauge",          PARAM_STRING|USE_FUNC_PARAM, (void*)prom_gauge_param},
+       {"xhttp_prom_timeout",  INT_PARAM,      &timeout_minutes},
+       {0, 0, 0}
+};
+
+struct module_exports exports = {
+       "xhttp_prom",
+       DEFAULT_DLFLAGS, /* dlopen flags */
+       cmds,
+       params,
+       0,              /* exported RPC methods */
+       0,              /* exported pseudo-variables */
+       0,              /* response function */
+       mod_init,       /* module initialization function */
+       0,              /* per child init function */
+       mod_destroy     /* destroy function */
+};
+
+/** Implementation of prom_fault function required by the management API.
+ *
+ * This function will be called whenever a management function
+ * indicates that an error ocurred while it was processing the request. The
+ * function takes the reply code and reason phrase as parameters, these will
+ * be put in the body of the reply.
+ *
+ * @param ctx A pointer to the context structure of the request being
+ *            processed.
+ * @param code Reason code.
+ * @param fmt Formatting string used to build the reason phrase.
+ */
+static void prom_fault(prom_ctx_t* ctx, int code, char* fmt, ...)
+{
+       va_list ap;
+       struct xhttp_prom_reply *reply = &ctx->reply;
+
+       reply->code = code;
+       va_start(ap, fmt);
+       vsnprintf(error_buf, ERROR_REASON_BUF_LEN, fmt, ap);
+       va_end(ap);
+       reply->reason.len = strlen(error_buf);
+       reply->reason.s = error_buf;
+       /* reset body so we can print the error */
+       reply->body.len = 0;
+
+       return;
+}
+
+static int mod_init(void)
+{
+       /* Register RPC commands. */
+       if (rpc_register_array(rpc_cmds) != 0) {
+               LM_ERR("failed to register RPC commands\n");
+               return -1;
+       }
+
+       /* bind the XHTTP API */
+       if (xhttp_load_api(&xhttp_api) < 0) {
+               LM_ERR("cannot bind to XHTTP API\n");
+               return -1;
+       }
+
+       /* Check xhttp_prom_buf_size param */
+       if (buf_size == 0)
+               buf_size = pkg_mem_size/3;
+
+       /* Check xhttp_prom_timeout param */
+       if (timeout_minutes == 0) {
+               timeout_minutes = 60;
+       }
+
+       /* Initialize Prometheus metrics. */
+       if (prom_metric_init(timeout_minutes)) {
+               LM_ERR("Cannot initialize Prometheus metrics\n");
+               return -1;
+       }
+
+       return 0;
+}
+
+static void mod_destroy(void)
+{
+       LM_DBG("cleaning up\n");
+
+       prom_metric_close();
+}
+
+/**
+ * Parse parameters to create a counter.
+ */
+int prom_counter_param(modparam_t type, void *val)
+{
+       return prom_counter_create((char*)val);
+}
+
+/**
+ * Parse parameters to create a gauge.
+ */
+int prom_gauge_param(modparam_t type, void *val)
+{
+       return prom_gauge_create((char*)val);
+}
+
+#define PROMETHEUS_URI "/metrics"
+static str prom_uri = str_init(PROMETHEUS_URI);
+
+static int ki_xhttp_prom_check_uri(sip_msg_t* msg)
+{
+       if(msg==NULL) {
+               LM_ERR("No message\n");
+               return -1;
+       }
+
+       str *uri = &msg->first_line.u.request.uri;
+       LM_DBG("URI: %.*s\n", uri->len, uri->s);
+       
+       if (STR_EQ(*uri, prom_uri)) {
+               LM_DBG("URI matches: %.*s\n", uri->len, uri->s);
+               /* Return True */
+               return 1;
+       }
+
+       /* Return False */
+       LM_DBG("URI does not match: %.*s (%.*s)\n", uri->len, uri->s,
+                  prom_uri.len, prom_uri.s);
+       return 0;
+}
+
+static int w_prom_check_uri(sip_msg_t* msg)
+{
+       if(msg==NULL) {
+               LM_ERR("No message\n");
+               return -1;
+       }
+
+       str *uri = &msg->first_line.u.request.uri;
+       LM_DBG("URI: %.*s\n", uri->len, uri->s);
+       
+       if (STR_EQ(*uri, prom_uri)) {
+               LM_DBG("URI matches: %.*s\n", uri->len, uri->s);
+               /* Return True */
+               return 1;
+       }
+
+       /* Return False */
+       LM_DBG("URI does not match: %.*s (%.*s)\n", uri->len, uri->s,
+                  prom_uri.len, prom_uri.s);
+       return -1;
+}
+
+/** Initialize xhttp_prom reply data structure.
+ *
+ * This function initializes the data structure that contains all data related
+ * to the xhttp_prom reply being created. The function must be called before any
+ * other function that adds data to the reply.
+ * @param ctx prom_ctx_t structure to be initialized.
+ * @return 0 on success, a negative number on error.
+ */
+static int init_xhttp_prom_reply(prom_ctx_t *ctx)
+{
+       struct xhttp_prom_reply *reply = &ctx->reply;
+
+       reply->code = 200;
+       reply->reason = XHTTP_PROM_REASON_OK;
+       reply->buf.s = pkg_malloc(buf_size);
+       if (!reply->buf.s) {
+               LM_ERR("oom\n");
+               prom_fault(ctx, 500, "Internal Server Error (No memory left)");
+               return -1;
+       }
+       reply->buf.len = buf_size;
+       reply->body.s = reply->buf.s;
+       reply->body.len = 0;
+       return 0;
+}
+
+/**
+ * Free buffer in reply.
+ */
+static void xhttp_prom_reply_free(prom_ctx_t *ctx)
+{
+       struct xhttp_prom_reply* reply;
+       reply = &ctx->reply;
+       
+       if (reply->buf.s) {
+               pkg_free(reply->buf.s);
+               reply->buf.s = NULL;
+               reply->buf.len = 0;
+       }
+
+       /* if (ctx->arg.s) { */
+       /*      pkg_free(ctx->arg.s); */
+       /*      ctx->arg.s = NULL; */
+       /*      ctx->arg.len = 0; */
+       /* } */
+       /* if (ctx->data_structs) { */
+       /*      free_data_struct(ctx->data_structs); */
+       /*      ctx->data_structs = NULL; */
+       /* } */
+}
+
+static int prom_send(prom_ctx_t* ctx)
+{
+       struct xhttp_prom_reply* reply;
+
+       if (ctx->reply_sent) return 1;
+
+       reply = &ctx->reply;
+
+       if (prom_stats_get(ctx, &xhttp_prom_stats)){
+               LM_DBG("prom_fault(500,\"Internal Server Error\"\n");
+               prom_fault(ctx, 500, "Internal Server Error");
+       }
+
+       ctx->reply_sent = 1;
+       if (reply->body.len)
+               xhttp_api.reply(ctx->msg, reply->code, &reply->reason,
+                       &XHTTP_PROM_CONTENT_TYPE_TEXT_HTML, &reply->body);
+       else {
+               LM_DBG("xhttp_api.reply(%p, %d, %.*s, %.*s, %.*s)\n",
+                          ctx->msg, reply->code,
+                          (&reply->reason)->len, (&reply->reason)->s,
+                          (&XHTTP_PROM_CONTENT_TYPE_TEXT_HTML)->len,
+                          (&XHTTP_PROM_CONTENT_TYPE_TEXT_HTML)->s,
+                          (&reply->reason)->len, (&reply->reason)->s);
+               
+               xhttp_api.reply(ctx->msg, reply->code, &reply->reason,
+                                               &XHTTP_PROM_CONTENT_TYPE_TEXT_HTML, &reply->reason);
+       }
+
+       xhttp_prom_reply_free(ctx);
+       
+       return 0;
+}
+
+
+static int ki_xhttp_prom_dispatch(sip_msg_t* msg)
+{
+       int ret = 0;
+
+       if(msg==NULL) {
+               LM_ERR("No message\n");
+               return -1;
+       }
+
+       if(!IS_HTTP(msg)) {
+               LM_DBG("Got non HTTP msg\n");
+               return NONSIP_MSG_PASS;
+       }
+
+       /* Init xhttp_prom context */
+       if (ctx.reply.buf.s) {
+               LM_ERR("Unexpected buf value [%p][%d]\n",
+                          ctx.reply.buf.s, ctx.reply.buf.len);
+
+               /* Something happened and this memory was not freed. */
+               xhttp_prom_reply_free(&ctx);
+       }
+       memset(&ctx, 0, sizeof(prom_ctx_t));
+       ctx.msg = msg;
+       if (init_xhttp_prom_reply(&ctx) < 0) {
+               goto send_reply;
+       }
+
+send_reply:
+       if (!ctx.reply_sent) {
+               ret = prom_send(&ctx);
+       }
+       if (ret < 0) {
+               return -1;
+       }
+       return 0;
+}
+
+static int w_prom_dispatch(sip_msg_t* msg)
+{
+       return ki_xhttp_prom_dispatch(msg);
+}
+
+static int fixup_metric_reset(void** param, int param_no)
+{
+       return fixup_spve_null(param, 1);
+}
+
+/* static int fixup_free_metric_reset(void** param, int param_no) */
+/* { */
+/*     return fixup_free_spve_null(param, 1); */
+/* } */
+
+/**
+ * Reset a counter (No labels)
+ */
+static int ki_xhttp_prom_counter_reset_l0(struct sip_msg* msg, str *s_name)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if (prom_counter_reset(s_name, NULL, NULL, NULL)) {
+               LM_ERR("Cannot reset counter: %.*s\n", s_name->len, s_name->s);
+               return -1;
+       }
+
+       LM_DBG("Counter %.*s reset\n", s_name->len, s_name->s);
+       return 1;
+}
+
+/**
+ * Reset a counter (1 label)
+ */
+static int ki_xhttp_prom_counter_reset_l1(struct sip_msg* msg, str *s_name, str *l1)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if (l1 == NULL || l1->s == NULL || l1->len == 0) {
+               LM_ERR("Invalid l1 string\n");
+               return -1;
+       }
+
+       if (prom_counter_reset(s_name, l1, NULL, NULL)) {
+               LM_ERR("Cannot reset counter: %.*s (%.*s)\n", s_name->len, s_name->s,
+                          l1->len, l1->s);
+               return -1;
+       }
+
+       LM_DBG("Counter %.*s (%.*s) reset\n", s_name->len, s_name->s,
+                  l1->len, l1->s);
+
+       return 1;
+}
+
+/**
+ * Reset a counter (2 labels)
+ */
+static int ki_xhttp_prom_counter_reset_l2(struct sip_msg* msg, str *s_name, str *l1, str *l2)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if (l1 == NULL || l1->s == NULL || l1->len == 0) {
+               LM_ERR("Invalid l1 string\n");
+               return -1;
+       }
+
+       if (l2 == NULL || l2->s == NULL || l2->len == 0) {
+               LM_ERR("Invalid l2 string\n");
+               return -1;
+       }
+
+       if (prom_counter_reset(s_name, l1, l2, NULL)) {
+               LM_ERR("Cannot reset counter: %.*s (%.*s, %.*s)\n", s_name->len, s_name->s,
+                          l1->len, l1->s,
+                          l2->len, l2->s
+                       );
+               return -1;
+       }
+
+       LM_DBG("Counter %.*s (%.*s, %.*s) reset\n", s_name->len, s_name->s,
+                  l1->len, l1->s,
+                  l2->len, l2->s
+               );
+
+       return 1;
+}
+
+/**
+ * Reset a counter (3 labels)
+ */
+static int ki_xhttp_prom_counter_reset_l3(struct sip_msg* msg, str *s_name, str *l1, str *l2,
+                                                                                 str *l3)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if (l1 == NULL || l1->s == NULL || l1->len == 0) {
+               LM_ERR("Invalid l1 string\n");
+               return -1;
+       }
+
+       if (l2 == NULL || l2->s == NULL || l2->len == 0) {
+               LM_ERR("Invalid l2 string\n");
+               return -1;
+       }
+
+       if (l3 == NULL || l3->s == NULL || l3->len == 0) {
+               LM_ERR("Invalid l3 string\n");
+               return -1;
+       }
+
+       if (prom_counter_reset(s_name, l1, l2, l3)) {
+               LM_ERR("Cannot reset counter: %.*s (%.*s, %.*s, %.*s)\n", s_name->len, s_name->s,
+                          l1->len, l1->s,
+                          l2->len, l2->s,
+                          l3->len, l3->s
+                       );
+               return -1;
+       }
+
+       LM_DBG("Counter %.*s (%.*s, %.*s, %.*s) reset\n", s_name->len, s_name->s,
+                  l1->len, l1->s,
+                  l2->len, l2->s,
+                  l3->len, l3->s
+               );
+
+       return 1;
+}
+
+/**
+ * Reset a counter.
+ */
+static int w_prom_counter_reset(struct sip_msg* msg, char* pname, char *l1, char *l2,
+                                                               char *l3)
+{
+       str s_name;
+
+       if (pname == NULL) {
+               LM_ERR("Invalid parameter\n");
+               return -1;
+       }
+
+       if (get_str_fparam(&s_name, msg, (gparam_t*)pname)!=0) {
+               LM_ERR("No counter name\n");
+               return -1;
+       }
+       if (s_name.s == NULL || s_name.len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       str l1_str, l2_str, l3_str;
+       if (l1 != NULL) {
+               if (get_str_fparam(&l1_str, msg, (gparam_t*)l1)!=0) {
+                       LM_ERR("No label l1 in counter\n");
+                       return -1;
+               }
+               if (l1_str.s == NULL || l1_str.len == 0) {
+                       LM_ERR("Invalid l1 string\n");
+                       return -1;
+               }
+
+               if (l2 != NULL) {
+                       if (get_str_fparam(&l2_str, msg, (gparam_t*)l2)!=0) {
+                               LM_ERR("No label l2 in counter\n");
+                               return -1;
+                       }
+                       if (l2_str.s == NULL || l2_str.len == 0) {
+                               LM_ERR("Invalid l2 string\n");
+                               return -1;
+                       }
+
+                       if (l3 != NULL) {
+                               if (get_str_fparam(&l3_str, msg, (gparam_t*)l3)!=0) {
+                                       LM_ERR("No label l3 in counter\n");
+                                       return -1;
+                               }
+                               if (l3_str.s == NULL || l3_str.len == 0) {
+                                       LM_ERR("Invalid l3 string\n");
+                                       return -1;
+                               }
+                       } /* if l3 != NULL */
+                       
+               } else {
+                       l3 = NULL;
+               } /* if l2 != NULL */
+               
+       } else {
+               l2 = NULL;
+               l3 = NULL;
+       } /* if l1 != NULL */
+       
+       if (prom_counter_reset(&s_name,
+                                                  (l1!=NULL)?&l1_str:NULL,
+                                                  (l2!=NULL)?&l2_str:NULL,
+                                                  (l3!=NULL)?&l3_str:NULL
+                       )) {
+               LM_ERR("Cannot reset counter: %.*s\n", s_name.len, s_name.s);
+               return -1;
+       }
+
+       LM_DBG("Counter %.*s reset\n", s_name.len, s_name.s);
+       return 1;
+}
+
+/**
+ * Reset a counter (no labels)
+ */
+static int w_prom_counter_reset_l0(struct sip_msg* msg, char* pname)
+{
+  return w_prom_counter_reset(msg, pname, NULL, NULL, NULL);
+}
+
+/**
+ * Reset a counter (one label)
+ */
+static int w_prom_counter_reset_l1(struct sip_msg* msg, char* pname, char *l1)
+{
+  return w_prom_counter_reset(msg, pname, l1, NULL, NULL);
+}
+
+/**
+ * Reset a counter (two labels)
+ */
+static int w_prom_counter_reset_l2(struct sip_msg* msg, char* pname, char *l1, char *l2)
+{
+  return w_prom_counter_reset(msg, pname, l1, l2, NULL);
+}
+
+/**
+ * Reset a counter (three labels)
+ */
+static int w_prom_counter_reset_l3(struct sip_msg* msg, char* pname, char *l1, char *l2,
+       char *l3)
+{
+  return w_prom_counter_reset(msg, pname, l1, l2, l3);
+}
+
+/**
+ * Reset a gauge (No labels)
+ */
+static int ki_xhttp_prom_gauge_reset_l0(struct sip_msg* msg, str *s_name)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if (prom_gauge_reset(s_name, NULL, NULL, NULL)) {
+               LM_ERR("Cannot reset gauge: %.*s\n", s_name->len, s_name->s);
+               return -1;
+       }
+
+       LM_DBG("Gauge %.*s reset\n", s_name->len, s_name->s);
+       return 1;
+}
+
+/**
+ * Reset a gauge (1 label)
+ */
+static int ki_xhttp_prom_gauge_reset_l1(struct sip_msg* msg, str *s_name, str *l1)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if (l1 == NULL || l1->s == NULL || l1->len == 0) {
+               LM_ERR("Invalid l1 string\n");
+               return -1;
+       }
+
+       if (prom_gauge_reset(s_name, l1, NULL, NULL)) {
+               LM_ERR("Cannot reset gauge: %.*s (%.*s)\n", s_name->len, s_name->s,
+                          l1->len, l1->s);
+               return -1;
+       }
+
+       LM_DBG("Gauge %.*s (%.*s) reset\n", s_name->len, s_name->s,
+                  l1->len, l1->s);
+
+       return 1;
+}
+
+/**
+ * Reset a gauge (2 labels)
+ */
+static int ki_xhttp_prom_gauge_reset_l2(struct sip_msg* msg, str *s_name, str *l1, str *l2)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if (l1 == NULL || l1->s == NULL || l1->len == 0) {
+               LM_ERR("Invalid l1 string\n");
+               return -1;
+       }
+
+       if (l2 == NULL || l2->s == NULL || l2->len == 0) {
+               LM_ERR("Invalid l2 string\n");
+               return -1;
+       }
+
+       if (prom_gauge_reset(s_name, l1, l2, NULL)) {
+               LM_ERR("Cannot reset gauge: %.*s (%.*s, %.*s)\n", s_name->len, s_name->s,
+                          l1->len, l1->s,
+                          l2->len, l2->s
+                       );
+               return -1;
+       }
+
+       LM_DBG("Gauge %.*s (%.*s, %.*s) reset\n", s_name->len, s_name->s,
+                  l1->len, l1->s,
+                  l2->len, l2->s
+               );
+
+       return 1;
+}
+
+/**
+ * Reset a gauge (3 labels)
+ */
+static int ki_xhttp_prom_gauge_reset_l3(struct sip_msg* msg, str *s_name, str *l1, str *l2,
+                                                                                 str *l3)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if (l1 == NULL || l1->s == NULL || l1->len == 0) {
+               LM_ERR("Invalid l1 string\n");
+               return -1;
+       }
+
+       if (l2 == NULL || l2->s == NULL || l2->len == 0) {
+               LM_ERR("Invalid l2 string\n");
+               return -1;
+       }
+
+       if (l3 == NULL || l3->s == NULL || l3->len == 0) {
+               LM_ERR("Invalid l3 string\n");
+               return -1;
+       }
+
+       if (prom_gauge_reset(s_name, l1, l2, l3)) {
+               LM_ERR("Cannot reset gauge: %.*s (%.*s, %.*s, %.*s)\n", s_name->len, s_name->s,
+                          l1->len, l1->s,
+                          l2->len, l2->s,
+                          l3->len, l3->s
+                       );
+               return -1;
+       }
+
+       LM_DBG("Gauge %.*s (%.*s, %.*s, %.*s) reset\n", s_name->len, s_name->s,
+                  l1->len, l1->s,
+                  l2->len, l2->s,
+                  l3->len, l3->s
+               );
+
+       return 1;
+}
+
+/**
+ * Reset a gauge.
+ */
+static int w_prom_gauge_reset(struct sip_msg* msg, char* pname, char *l1, char *l2,
+                                                               char *l3)
+{
+       str s_name;
+
+       if (pname == NULL) {
+               LM_ERR("Invalid parameter\n");
+               return -1;
+       }
+
+       if (get_str_fparam(&s_name, msg, (gparam_t*)pname)!=0) {
+               LM_ERR("No gauge name\n");
+               return -1;
+       }
+       if (s_name.s == NULL || s_name.len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       str l1_str, l2_str, l3_str;
+       if (l1 != NULL) {
+               if (get_str_fparam(&l1_str, msg, (gparam_t*)l1)!=0) {
+                       LM_ERR("No label l1 in gauge\n");
+                       return -1;
+               }
+               if (l1_str.s == NULL || l1_str.len == 0) {
+                       LM_ERR("Invalid l1 string\n");
+                       return -1;
+               }
+
+               if (l2 != NULL) {
+                       if (get_str_fparam(&l2_str, msg, (gparam_t*)l2)!=0) {
+                               LM_ERR("No label l2 in gauge\n");
+                               return -1;
+                       }
+                       if (l2_str.s == NULL || l2_str.len == 0) {
+                               LM_ERR("Invalid l2 string\n");
+                               return -1;
+                       }
+
+                       if (l3 != NULL) {
+                               if (get_str_fparam(&l3_str, msg, (gparam_t*)l3)!=0) {
+                                       LM_ERR("No label l3 in gauge\n");
+                                       return -1;
+                               }
+                               if (l3_str.s == NULL || l3_str.len == 0) {
+                                       LM_ERR("Invalid l3 string\n");
+                                       return -1;
+                               }
+                       } /* if l3 != NULL */
+                       
+               } else {
+                       l3 = NULL;
+               } /* if l2 != NULL */
+               
+       } else {
+               l2 = NULL;
+               l3 = NULL;
+       } /* if l1 != NULL */
+       
+       if (prom_gauge_reset(&s_name,
+                                                (l1!=NULL)?&l1_str:NULL,
+                                                (l2!=NULL)?&l2_str:NULL,
+                                                (l3!=NULL)?&l3_str:NULL
+                       )) {
+               LM_ERR("Cannot reset gauge: %.*s\n", s_name.len, s_name.s);
+               return -1;
+       }
+
+       LM_DBG("Gauge %.*s reset\n", s_name.len, s_name.s);
+       return 1;
+}
+
+/**
+ * Reset a gauge (no labels)
+ */
+static int w_prom_gauge_reset_l0(struct sip_msg* msg, char* pname)
+{
+  return w_prom_gauge_reset(msg, pname, NULL, NULL, NULL);
+}
+
+/**
+ * Reset a gauge (one label)
+ */
+static int w_prom_gauge_reset_l1(struct sip_msg* msg, char* pname, char *l1)
+{
+  return w_prom_gauge_reset(msg, pname, l1, NULL, NULL);
+}
+
+/**
+ * Reset a gauge (two labels)
+ */
+static int w_prom_gauge_reset_l2(struct sip_msg* msg, char* pname, char *l1, char *l2)
+{
+  return w_prom_gauge_reset(msg, pname, l1, l2, NULL);
+}
+
+/**
+ * Reset a gauge (three labels)
+ */
+static int w_prom_gauge_reset_l3(struct sip_msg* msg, char* pname, char *l1, char *l2,
+       char *l3)
+{
+  return w_prom_gauge_reset(msg, pname, l1, l2, l3);
+}
+
+static int fixup_counter_inc(void** param, int param_no)
+{
+       if (param_no == 1 || param_no == 2) {
+               return fixup_spve_igp(param, param_no);
+       } else {
+               return fixup_spve_null(param, 1);
+       }
+}
+
+/* static int fixup_free_counter_inc(void** param, int param_no) */
+/* { */
+/*     if (param_no == 1 || param_no == 2) { */
+/*             return fixup_free_spve_igp(param, param_no); */
+/*     } else { */
+/*             return fixup_free_spve_null(param, 1); */
+/*     } */
+/* } */
+
+/**
+ * Add an integer to a counter (No labels).
+ */
+static int ki_xhttp_prom_counter_inc_l0(struct sip_msg* msg, str *s_name, int number)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if(number < 0) {
+               LM_ERR("invalid negative number parameter\n");
+               return -1;
+       }
+
+       if (prom_counter_inc(s_name, number, NULL, NULL, NULL)) {
+               LM_ERR("Cannot add number: %d to counter: %.*s\n", number, s_name->len, s_name->s);
+               return -1;
+       }
+
+       LM_DBG("Added %d to counter %.*s\n", number, s_name->len, s_name->s);
+       return 1;
+}
+
+/**
+ * Add an integer to a counter (1 label).
+ */
+static int ki_xhttp_prom_counter_inc_l1(struct sip_msg* msg, str *s_name, int number, str *l1)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if(number < 0) {
+               LM_ERR("invalid negative number parameter\n");
+               return -1;
+       }
+
+       if (l1 == NULL || l1->s == NULL || l1->len == 0) {
+               LM_ERR("Invalid l1 string\n");
+               return -1;
+       }
+
+       if (prom_counter_inc(s_name, number, l1, NULL, NULL)) {
+               LM_ERR("Cannot add number: %d to counter: %.*s (%.*s)\n",
+                          number, s_name->len, s_name->s,
+                          l1->len, l1->s
+                       );
+               return -1;
+       }
+
+       LM_DBG("Added %d to counter %.*s (%.*s)\n", number,
+                  s_name->len, s_name->s,
+                  l1->len, l1->s
+               );
+
+       return 1;
+}
+
+/**
+ * Add an integer to a counter (2 labels).
+ */
+static int ki_xhttp_prom_counter_inc_l2(struct sip_msg* msg, str *s_name, int number,
+                                                                               str *l1, str *l2)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if(number < 0) {
+               LM_ERR("invalid negative number parameter\n");
+               return -1;
+       }
+
+       if (l1 == NULL || l1->s == NULL || l1->len == 0) {
+               LM_ERR("Invalid l1 string\n");
+               return -1;
+       }
+
+       if (l2 == NULL || l2->s == NULL || l2->len == 0) {
+               LM_ERR("Invalid l2 string\n");
+               return -1;
+       }
+
+       if (prom_counter_inc(s_name, number, l1, l2, NULL)) {
+               LM_ERR("Cannot add number: %d to counter: %.*s (%.*s, %.*s)\n",
+                          number, s_name->len, s_name->s,
+                          l1->len, l1->s,
+                          l2->len, l2->s
+                       );
+               return -1;
+       }
+
+       LM_DBG("Added %d to counter %.*s (%.*s, %.*s)\n", number,
+                  s_name->len, s_name->s,
+                  l1->len, l1->s,
+                  l2->len, l2->s
+               );
+
+       return 1;
+}
+
+/**
+ * Add an integer to a counter (3 labels).
+ */
+static int ki_xhttp_prom_counter_inc_l3(struct sip_msg* msg, str *s_name, int number,
+                                                                               str *l1, str *l2, str *l3)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if(number < 0) {
+               LM_ERR("invalid negative number parameter\n");
+               return -1;
+       }
+
+       if (l1 == NULL || l1->s == NULL || l1->len == 0) {
+               LM_ERR("Invalid l1 string\n");
+               return -1;
+       }
+
+       if (l2 == NULL || l2->s == NULL || l2->len == 0) {
+               LM_ERR("Invalid l2 string\n");
+               return -1;
+       }
+
+       if (l3 == NULL || l3->s == NULL || l3->len == 0) {
+               LM_ERR("Invalid l3 string\n");
+               return -1;
+       }
+
+       if (prom_counter_inc(s_name, number, l1, l2, l3)) {
+               LM_ERR("Cannot add number: %d to counter: %.*s (%.*s, %.*s, %.*s)\n",
+                          number, s_name->len, s_name->s,
+                          l1->len, l1->s,
+                          l2->len, l2->s,
+                          l3->len, l3->s
+                       );
+               return -1;
+       }
+
+       LM_DBG("Added %d to counter %.*s (%.*s, %.*s, %.*s)\n", number,
+                  s_name->len, s_name->s,
+                  l1->len, l1->s,
+                  l2->len, l2->s,
+                  l3->len, l3->s
+               );
+
+       return 1;
+}
+
+/**
+ * Add an integer to a counter.
+ */
+static int w_prom_counter_inc(struct sip_msg* msg, char *pname, char* pnumber,
+                                                         char *l1, char *l2, char *l3)
+{
+       int number;
+       str s_name;
+
+       if (pname == NULL || pnumber == 0) {
+               LM_ERR("Invalid parameters\n");
+               return -1;
+       }
+
+       if (get_str_fparam(&s_name, msg, (gparam_t*)pname)!=0) {
+               LM_ERR("No counter name\n");
+               return -1;
+       }
+       if (s_name.s == NULL || s_name.len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if(get_int_fparam(&number, msg, (gparam_p)pnumber)!=0) {
+               LM_ERR("no number\n");
+               return -1;
+       }
+       if(number < 0) {
+               LM_ERR("invalid negative number parameter\n");
+               return -1;
+       }
+
+       str l1_str, l2_str, l3_str;
+       if (l1 != NULL) {
+               if (get_str_fparam(&l1_str, msg, (gparam_t*)l1)!=0) {
+                       LM_ERR("No label l1 in counter\n");
+                       return -1;
+               }
+               if (l1_str.s == NULL || l1_str.len == 0) {
+                       LM_ERR("Invalid l1 string\n");
+                       return -1;
+               }
+
+               if (l2 != NULL) {
+                       if (get_str_fparam(&l2_str, msg, (gparam_t*)l2)!=0) {
+                               LM_ERR("No label l2 in counter\n");
+                               return -1;
+                       }
+                       if (l2_str.s == NULL || l2_str.len == 0) {
+                               LM_ERR("Invalid l2 string\n");
+                               return -1;
+                       }
+
+                       if (l3 != NULL) {
+                               if (get_str_fparam(&l3_str, msg, (gparam_t*)l3)!=0) {
+                                       LM_ERR("No label l3 in counter\n");
+                                       return -1;
+                               }
+                               if (l3_str.s == NULL || l3_str.len == 0) {
+                                       LM_ERR("Invalid l3 string\n");
+                                       return -1;
+                               }
+                       } /* if l3 != NULL */
+                       
+               } else {
+                       l3 = NULL;
+               } /* if l2 != NULL */
+               
+       } else {
+               l2 = NULL;
+               l3 = NULL;
+       } /* if l1 != NULL */
+
+       if (prom_counter_inc(&s_name, number,
+                                                  (l1!=NULL)?&l1_str:NULL,
+                                                  (l2!=NULL)?&l2_str:NULL,
+                                                  (l3!=NULL)?&l3_str:NULL
+                                                )) {
+               LM_ERR("Cannot add number: %d to counter: %.*s\n", number, s_name.len, s_name.s);
+               return -1;
+       }
+
+       LM_DBG("Added %d to counter %.*s\n", number, s_name.len, s_name.s);
+       return 1;
+}
+
+/**
+ * Add an integer to a counter (no labels)
+ */
+static int w_prom_counter_inc_l0(struct sip_msg* msg, char *pname, char* pnumber)
+{
+       return w_prom_counter_inc(msg, pname, pnumber, NULL, NULL, NULL);
+}
+
+/**
+ * Add an integer to a counter (1 labels)
+ */
+static int w_prom_counter_inc_l1(struct sip_msg* msg, char *pname, char* pnumber,
+                                                                char *l1)
+{
+       return w_prom_counter_inc(msg, pname, pnumber, l1, NULL, NULL);
+}
+
+/**
+ * Add an integer to a counter (2 labels)
+ */
+static int w_prom_counter_inc_l2(struct sip_msg* msg, char *pname, char* pnumber,
+                                                                char *l1, char *l2)
+{
+       return w_prom_counter_inc(msg, pname, pnumber, l1, l2, NULL);
+}
+
+/**
+ * Add an integer to a counter (3 labels)
+ */
+static int w_prom_counter_inc_l3(struct sip_msg* msg, char *pname, char* pnumber,
+                                                                char *l1, char *l2, char *l3)
+{
+       return w_prom_counter_inc(msg, pname, pnumber, l1, l2, l3);
+}
+
+/**
+ * Parse a string and convert to double.
+ *
+ * /param s_number pointer to number string.
+ * /param number double passed as reference.
+ *
+ * /return 0 on success.
+ * On error value pointed by pnumber is undefined.
+ */
+static int double_parse_str(str *s_number, double *pnumber)
+{
+       char *s = NULL;
+       
+       if (!s_number || !s_number->s || s_number->len == 0) {
+               LM_ERR("Bad s_number to convert to double\n");
+               goto error;
+       }
+
+       if (!pnumber) {
+               LM_ERR("No double passed by reference\n");
+               goto error;
+       }
+
+       /* We generate a zero terminated string. */
+
+       /* We set last character to zero to get a zero terminated string. */
+       int len = s_number->len;
+       s = pkg_malloc(len + 1);
+       if (!s) {
+               LM_ERR("Out of pkg memory\n");
+               goto error;
+       }
+       memcpy(s, s_number->s, len);
+       s[len] = '\0'; /* Zero terminated string. */
+
+       /* atof function does not check for errors. */
+       double num = atof(s);
+       LM_DBG("double number (%.*s) -> %f\n", len, s, num);
+
+       *pnumber = num;
+       pkg_free(s);
+       return 0;
+
+error:
+       if (s) {
+               pkg_free(s);
+       }
+       return -1;
+}
+
+/**
+ * Set a number to a gauge (No labels).
+ */
+static int ki_xhttp_prom_gauge_set_l0(struct sip_msg* msg, str *s_name, str *s_number)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if (s_number == NULL || s_number->s == NULL || s_number->len == 0) {
+               LM_ERR("Invalid number string\n");
+               return -1;
+       }
+
+       double number;
+       if (double_parse_str(s_number, &number)) {
+               LM_ERR("Cannot parse double\n");
+               return -1;
+       }
+
+       if (prom_gauge_set(s_name, number, NULL, NULL, NULL)) {
+               LM_ERR("Cannot assign number: %f to gauge: %.*s\n", number, s_name->len, s_name->s);
+               return -1;
+       }
+
+       LM_DBG("Assigned %f to gauge %.*s\n", number, s_name->len, s_name->s);
+       return 1;
+}
+
+/**
+ * Assign a number to a gauge (1 label).
+ */
+static int ki_xhttp_prom_gauge_set_l1(struct sip_msg* msg, str *s_name, str *s_number, str *l1)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if (s_number == NULL || s_number->s == NULL || s_number->len == 0) {
+               LM_ERR("Invalid number string\n");
+               return -1;
+       }
+
+       double number;
+       if (double_parse_str(s_number, &number)) {
+               LM_ERR("Cannot parse double\n");
+               return -1;
+       }
+
+       if (l1 == NULL || l1->s == NULL || l1->len == 0) {
+               LM_ERR("Invalid l1 string\n");
+               return -1;
+       }
+
+       if (prom_gauge_set(s_name, number, l1, NULL, NULL)) {
+               LM_ERR("Cannot assign number: %f to gauge: %.*s (%.*s)\n",
+                          number, s_name->len, s_name->s,
+                          l1->len, l1->s
+                       );
+               return -1;
+       }
+
+       LM_DBG("Assign %f to gauge %.*s (%.*s)\n", number,
+                  s_name->len, s_name->s,
+                  l1->len, l1->s
+               );
+       return 1;
+}
+
+/**
+ * Assign a number to a gauge (2 labels).
+ */
+static int ki_xhttp_prom_gauge_set_l2(struct sip_msg* msg, str *s_name, str *s_number,
+                                                                         str *l1, str *l2)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if (s_number == NULL || s_number->s == NULL || s_number->len == 0) {
+               LM_ERR("Invalid number string\n");
+               return -1;
+       }
+
+       double number;
+       if (double_parse_str(s_number, &number)) {
+               LM_ERR("Cannot parse double\n");
+               return -1;
+       }
+
+       if (l1 == NULL || l1->s == NULL || l1->len == 0) {
+               LM_ERR("Invalid l1 string\n");
+               return -1;
+       }
+
+       if (l2 == NULL || l2->s == NULL || l2->len == 0) {
+               LM_ERR("Invalid l2 string\n");
+               return -1;
+       }
+
+       if (prom_gauge_set(s_name, number, l1, l2, NULL)) {
+               LM_ERR("Cannot assign number: %f to gauge: %.*s (%.*s, %.*s)\n",
+                          number, s_name->len, s_name->s,
+                          l1->len, l1->s,
+                          l2->len, l2->s
+                       );
+               return -1;
+       }
+
+       LM_DBG("Assign %f to gauge %.*s (%.*s, %.*s)\n", number,
+                  s_name->len, s_name->s,
+                  l1->len, l1->s,
+                  l2->len, l2->s
+               );
+
+       return 1;
+}
+
+/**
+ * Assign a number to a gauge (3 labels).
+ */
+static int ki_xhttp_prom_gauge_set_l3(struct sip_msg* msg, str *s_name, str *s_number,
+                                                                         str *l1, str *l2, str *l3)
+{
+       if (s_name == NULL || s_name->s == NULL || s_name->len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if (s_number == NULL || s_number->s == NULL || s_number->len == 0) {
+               LM_ERR("Invalid number string\n");
+               return -1;
+       }
+
+       double number;
+       if (double_parse_str(s_number, &number)) {
+               LM_ERR("Cannot parse double\n");
+               return -1;
+       }
+
+       if (l1 == NULL || l1->s == NULL || l1->len == 0) {
+               LM_ERR("Invalid l1 string\n");
+               return -1;
+       }
+
+       if (l2 == NULL || l2->s == NULL || l2->len == 0) {
+               LM_ERR("Invalid l2 string\n");
+               return -1;
+       }
+
+       if (l3 == NULL || l3->s == NULL || l3->len == 0) {
+               LM_ERR("Invalid l3 string\n");
+               return -1;
+       }
+
+       if (prom_gauge_set(s_name, number, l1, l2, l3)) {
+               LM_ERR("Cannot assign number: %f to gauge: %.*s (%.*s, %.*s, %.*s)\n",
+                          number, s_name->len, s_name->s,
+                          l1->len, l1->s,
+                          l2->len, l2->s,
+                          l3->len, l3->s
+                       );
+               return -1;
+       }
+
+       LM_DBG("Assign %f to gauge %.*s (%.*s, %.*s, %.*s)\n", number,
+                  s_name->len, s_name->s,
+                  l1->len, l1->s,
+                  l2->len, l2->s,
+                  l3->len, l3->s
+               );
+
+       return 1;
+}
+
+/**
+ * Assign a number to a gauge.
+ */
+static int w_prom_gauge_set(struct sip_msg* msg, char *pname, char* pnumber,
+                                                       char *l1, char *l2, char *l3)
+{
+    str s_number;
+       str s_name;
+
+       if (pname == NULL || pnumber == 0) {
+               LM_ERR("Invalid parameters\n");
+               return -1;
+       }
+
+       if (get_str_fparam(&s_name, msg, (gparam_t*)pname)!=0) {
+               LM_ERR("No gauge name\n");
+               return -1;
+       }
+       if (s_name.s == NULL || s_name.len == 0) {
+               LM_ERR("Invalid name string\n");
+               return -1;
+       }
+
+       if (get_str_fparam(&s_number, msg, (gparam_t*)pnumber)!=0) {
+               LM_ERR("No gauge number\n");
+               return -1;
+       }
+       if (s_number.s == NULL || s_number.len == 0) {
+               LM_ERR("Invalid number string\n");
+               return -1;
+       }
+
+       double number;
+       if (double_parse_str(&s_number, &number)) {
+               LM_ERR("Cannot parse double\n");
+               return -1;
+       }
+
+       str l1_str, l2_str, l3_str;
+       if (l1 != NULL) {
+               if (get_str_fparam(&l1_str, msg, (gparam_t*)l1)!=0) {
+                       LM_ERR("No label l1 in counter\n");
+                       return -1;
+               }
+               if (l1_str.s == NULL || l1_str.len == 0) {
+                       LM_ERR("Invalid l1 string\n");
+                       return -1;
+               }
+
+               if (l2 != NULL) {
+                       if (get_str_fparam(&l2_str, msg, (gparam_t*)l2)!=0) {
+                               LM_ERR("No label l2 in counter\n");
+                               return -1;
+                       }
+                       if (l2_str.s == NULL || l2_str.len == 0) {
+                               LM_ERR("Invalid l2 string\n");
+                               return -1;
+                       }
+
+                       if (l3 != NULL) {
+                               if (get_str_fparam(&l3_str, msg, (gparam_t*)l3)!=0) {
+                                       LM_ERR("No label l3 in counter\n");
+                                       return -1;
+                               }
+                               if (l3_str.s == NULL || l3_str.len == 0) {
+                                       LM_ERR("Invalid l3 string\n");
+                                       return -1;
+                               }
+                       } /* if l3 != NULL */
+                       
+               } else {
+                       l3 = NULL;
+               } /* if l2 != NULL */
+               
+       } else {
+               l2 = NULL;
+               l3 = NULL;
+       } /* if l1 != NULL */
+
+       if (prom_gauge_set(&s_name, number,
+                                          (l1!=NULL)?&l1_str:NULL,
+                                          (l2!=NULL)?&l2_str:NULL,
+                                          (l3!=NULL)?&l3_str:NULL
+                       )) {
+               LM_ERR("Cannot assign number: %f to gauge: %.*s\n", number, s_name.len, s_name.s);
+               return -1;
+       }
+
+       LM_DBG("Assign %f to gauge %.*s\n", number, s_name.len, s_name.s);
+       return 1;
+}
+
+/**
+ * Assign a number to a gauge (no labels)
+ */
+static int w_prom_gauge_set_l0(struct sip_msg* msg, char *pname, char* pnumber)
+{
+       return w_prom_gauge_set(msg, pname, pnumber, NULL, NULL, NULL);
+}
+
+/**
+ * Assign a number to a gauge (1 labels)
+ */
+static int w_prom_gauge_set_l1(struct sip_msg* msg, char *pname, char* pnumber,
+                                                          char *l1)
+{
+       return w_prom_gauge_set(msg, pname, pnumber, l1, NULL, NULL);
+}
+
+/**
+ * Assign a number to a gauge (2 labels)
+ */
+static int w_prom_gauge_set_l2(struct sip_msg* msg, char *pname, char* pnumber,
+                                                          char *l1, char *l2)
+{
+       return w_prom_gauge_set(msg, pname, pnumber, l1, l2, NULL);
+}
+
+/**
+ * Assign a number to a gauge (3 labels)
+ */
+static int w_prom_gauge_set_l3(struct sip_msg* msg, char *pname, char* pnumber,
+                                                          char *l1, char *l2, char *l3)
+{
+       return w_prom_gauge_set(msg, pname, pnumber, l1, l2, l3);
+}
+
+/**
+ *
+ */
+/* clang-format off */
+static sr_kemi_t sr_kemi_xhttp_prom_exports[] = {
+       { str_init("xhttp_prom"), str_init("dispatch"),
+               SR_KEMIP_INT, ki_xhttp_prom_dispatch,
+               { SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
+                       SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("check_uri"),
+               SR_KEMIP_INT, ki_xhttp_prom_check_uri,
+               { SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
+                       SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("counter_reset_l0"),
+           SR_KEMIP_INT, ki_xhttp_prom_counter_reset_l0,
+               { SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
+                       SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("counter_reset_l1"),
+           SR_KEMIP_INT, ki_xhttp_prom_counter_reset_l1,
+               { SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
+                       SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("counter_reset_l2"),
+           SR_KEMIP_INT, ki_xhttp_prom_counter_reset_l2,
+               { SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
+                       SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("counter_reset_l3"),
+           SR_KEMIP_INT, ki_xhttp_prom_counter_reset_l3,
+               { SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
+                       SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("gauge_reset_l0"),
+           SR_KEMIP_INT, ki_xhttp_prom_gauge_reset_l0,
+               { SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
+                       SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("gauge_reset_l1"),
+           SR_KEMIP_INT, ki_xhttp_prom_gauge_reset_l1,
+               { SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
+                       SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("gauge_reset_l2"),
+           SR_KEMIP_INT, ki_xhttp_prom_gauge_reset_l2,
+               { SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
+                       SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("gauge_reset_l3"),
+           SR_KEMIP_INT, ki_xhttp_prom_gauge_reset_l3,
+               { SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
+                       SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("counter_inc_l0"),
+           SR_KEMIP_INT, ki_xhttp_prom_counter_inc_l0,
+               { SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_NONE,
+                       SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("counter_inc_l1"),
+           SR_KEMIP_INT, ki_xhttp_prom_counter_inc_l1,
+               { SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_STR,
+                       SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("counter_inc_l2"),
+           SR_KEMIP_INT, ki_xhttp_prom_counter_inc_l2,
+               { SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_STR,
+                       SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("counter_inc_l3"),
+           SR_KEMIP_INT, ki_xhttp_prom_counter_inc_l3,
+               { SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_STR,
+                       SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("gauge_set_l0"),
+           SR_KEMIP_INT, ki_xhttp_prom_gauge_set_l0,
+               { SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
+                       SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("gauge_set_l1"),
+           SR_KEMIP_INT, ki_xhttp_prom_gauge_set_l1,
+               { SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
+                       SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("gauge_set_l2"),
+           SR_KEMIP_INT, ki_xhttp_prom_gauge_set_l2,
+               { SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
+                       SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE }
+       },
+       { str_init("xhttp_prom"), str_init("gauge_set_l3"),
+           SR_KEMIP_INT, ki_xhttp_prom_gauge_set_l3,
+               { SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
+                       SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE }
+       },
+
+       { {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
+};
+/* clang-format on */
+
+/**
+ *
+ */
+int mod_register(char *path, int *dlflags, void *p1, void *p2)
+{
+       sr_kemi_modules_add(sr_kemi_xhttp_prom_exports);
+       return 0;
+}
+
+/* RPC commands. */
+
+/* NOTE: I rename ctx variable in rpc functions to avoid collision with global ctx. */
+
+static void rpc_prom_counter_reset(rpc_t *rpc, void *ct)
+{
+       str s_name;
+
+       if (rpc->scan(ct, "S", &s_name) < 1) {
+               rpc->fault(ct, 400, "required counter identifier");
+               return;
+       }
+
+       if (s_name.len == 0 || s_name.s == NULL) {
+               rpc->fault(ct, 400, "invalid counter identifier");
+               return;
+       }
+
+       str l1, l2, l3;
+       int res;
+       res = rpc->scan(ct, "*SSS", &l1, &l2, &l3);
+       if (res == 0) {
+               /* No labels */
+               if (prom_counter_reset(&s_name, NULL, NULL, NULL)) {
+                       LM_ERR("Cannot reset counter: %.*s\n", s_name.len, s_name.s);
+                       rpc->fault(ct, 500, "Failed to reset counter: %.*s", s_name.len, s_name.s);
+                       return;
+               }
+               LM_DBG("Counter reset: (%.*s)\n", s_name.len, s_name.s);
+               
+       } else if (res == 1) {
+               if (prom_counter_reset(&s_name, &l1, NULL, NULL)) {
+                       LM_ERR("Cannot reset counter: %.*s (%.*s)\n", s_name.len, s_name.s,
+                                  l1.len, l1.s);
+                       rpc->fault(ct, 500, "Failed to reset counter: %.*s (%.*s)", s_name.len, s_name.s,
+                                          l1.len, l1.s);
+                       return;
+               }
+               LM_DBG("Counter reset: %.*s (%.*s)\n", s_name.len, s_name.s,
+                          l1.len, l1.s);
+
+       } else if (res == 2) {
+               if (prom_counter_reset(&s_name, &l1, &l2, NULL)) {
+                       LM_ERR("Cannot reset counter: %.*s (%.*s, %.*s)\n", s_name.len, s_name.s,
+                                  l1.len, l1.s,
+                                  l2.len, l2.s);
+                       rpc->fault(ct, 500, "Failed to reset counter: %.*s (%.*s, %.*s)",
+                                          s_name.len, s_name.s,
+                                          l1.len, l1.s,
+                                          l2.len, l2.s
+                               );
+                       return;
+               }
+               LM_DBG("Counter reset: %.*s (%.*s, %.*s)\n", s_name.len, s_name.s,
+                          l1.len, l1.s,
+                          l2.len, l2.s);
+
+       } else if (res == 3) {
+               if (prom_counter_reset(&s_name, &l1, &l2, &l3)) {
+                       LM_ERR("Cannot reset counter: %.*s (%.*s, %.*s, %.*s)\n", s_name.len, s_name.s,
+                                  l1.len, l1.s,
+                                  l2.len, l2.s,
+                                  l3.len, l3.s
+                               );
+                       rpc->fault(ct, 500, "Failed to reset counter: %.*s (%.*s, %.*s, %.*s)",
+                                          s_name.len, s_name.s,
+                                          l1.len, l1.s,
+                                          l2.len, l2.s,
+                                          l3.len, l3.s
+                               );
+                       return;
+               }
+               LM_DBG("Counter reset: %.*s (%.*s, %.*s, %.*s)\n", s_name.len, s_name.s,
+                          l1.len, l1.s,
+                          l2.len, l2.s,
+                          l3.len, l3.s
+                       );
+
+       } else {
+               LM_ERR("Strange return value: %d\n", res);
+               rpc->fault(ct, 500, "Strange return value: %d", res);
+
+       } /* if res == 0 */
+               
+       return;
+}
+
+static void rpc_prom_counter_inc(rpc_t *rpc, void *ct)
+{
+       str s_name;
+
+       if (rpc->scan(ct, "S", &s_name) < 1) {
+               rpc->fault(ct, 400, "required counter identifier");
+               return;
+       }
+
+       if (s_name.len == 0 || s_name.s == NULL) {
+               rpc->fault(ct, 400, "invalid counter identifier");
+               return;
+       }
+
+       int number;
+       if (rpc->scan(ct, "d", &number) < 1) {
+               rpc->fault(ct, 400, "required number argument");
+               return;
+       }
+       if(number < 0) {
+               LM_ERR("invalid negative number parameter\n");
+               return;
+       }
+
+       str l1, l2, l3;
+       int res;
+       res = rpc->scan(ct, "*SSS", &l1, &l2, &l3);
+       if (res == 0) {
+               /* No labels */
+               if (prom_counter_inc(&s_name, number, NULL, NULL, NULL)) {
+                       LM_ERR("Cannot add %d to counter: %.*s\n", number, s_name.len, s_name.s);
+                       rpc->fault(ct, 500, "Failed to add %d to counter: %.*s", number,
+                                          s_name.len, s_name.s);
+                       return;
+               }
+               LM_DBG("Added %d to counter: (%.*s)\n", number, s_name.len, s_name.s);
+               
+       } else if (res == 1) {
+               if (prom_counter_inc(&s_name, number, &l1, NULL, NULL)) {
+                       LM_ERR("Cannot add %d to counter: %.*s (%.*s)\n", number, s_name.len, s_name.s,
+                                  l1.len, l1.s);
+                       rpc->fault(ct, 500, "Failed to add %d to counter: %.*s (%.*s)",
+                                          number, s_name.len, s_name.s,
+                                          l1.len, l1.s);
+                       return;
+               }
+               LM_DBG("Added %d to counter: %.*s (%.*s)\n", number, s_name.len, s_name.s,
+                          l1.len, l1.s);
+
+       } else if (res == 2) {
+               if (prom_counter_inc(&s_name, number, &l1, &l2, NULL)) {
+                       LM_ERR("Cannot add %d to counter: %.*s (%.*s, %.*s)\n", number,
+                                  s_name.len, s_name.s,
+                                  l1.len, l1.s,
+                                  l2.len, l2.s);
+                       rpc->fault(ct, 500, "Failed to add %d to counter: %.*s (%.*s, %.*s)",
+                                          number, s_name.len, s_name.s,
+                                          l1.len, l1.s,
+                                          l2.len, l2.s
+                               );
+                       return;
+               }
+               LM_DBG("Added %d to counter: %.*s (%.*s, %.*s)\n", number, s_name.len, s_name.s,
+                          l1.len, l1.s,
+                          l2.len, l2.s);
+
+       } else if (res == 3) {
+               if (prom_counter_inc(&s_name, number, &l1, &l2, &l3)) {
+                       LM_ERR("Cannot add %d to counter: %.*s (%.*s, %.*s, %.*s)\n",
+                                  number, s_name.len, s_name.s,
+                                  l1.len, l1.s,
+                                  l2.len, l2.s,
+                                  l3.len, l3.s
+                               );
+                       rpc->fault(ct, 500, "Failed to add %d to counter: %.*s (%.*s, %.*s, %.*s)",
+                                          number, s_name.len, s_name.s,
+                                          l1.len, l1.s,
+                                          l2.len, l2.s,
+                                          l3.len, l3.s
+                               );
+                       return;
+               }
+               LM_DBG("Added %d to counter: %.*s (%.*s, %.*s, %.*s)\n", number, s_name.len, s_name.s,
+                          l1.len, l1.s,
+                          l2.len, l2.s,
+                          l3.len, l3.s
+                       );
+
+       } else {
+               LM_ERR("Strange return value: %d\n", res);
+               rpc->fault(ct, 500, "Strange return value: %d", res);
+
+       } /* if res == 0 */
+
+       return;
+}
+
+static void rpc_prom_gauge_reset(rpc_t *rpc, void *ct)
+{
+       str s_name;
+
+       if (rpc->scan(ct, "S", &s_name) < 1) {
+               rpc->fault(ct, 400, "required gauge identifier");
+               return;
+       }
+
+       if (s_name.len == 0 || s_name.s == NULL) {
+               rpc->fault(ct, 400, "invalid gauge identifier");
+               return;
+       }
+
+       str l1, l2, l3;
+       int res;
+       res = rpc->scan(ct, "*SSS", &l1, &l2, &l3);
+       if (res == 0) {
+               /* No labels */
+               if (prom_gauge_reset(&s_name, NULL, NULL, NULL)) {
+                       LM_ERR("Cannot reset gauge: %.*s\n", s_name.len, s_name.s);
+                       rpc->fault(ct, 500, "Failed to reset gauge: %.*s", s_name.len, s_name.s);
+                       return;
+               }
+               LM_DBG("Gauge reset: (%.*s)\n", s_name.len, s_name.s);
+               
+       } else if (res == 1) {
+               if (prom_gauge_reset(&s_name, &l1, NULL, NULL)) {
+                       LM_ERR("Cannot reset gauge: %.*s (%.*s)\n", s_name.len, s_name.s,
+                                  l1.len, l1.s);
+                       rpc->fault(ct, 500, "Failed to reset gauge: %.*s (%.*s)", s_name.len, s_name.s,
+                                          l1.len, l1.s);
+                       return;
+               }
+               LM_DBG("Gauge reset: %.*s (%.*s)\n", s_name.len, s_name.s,
+                          l1.len, l1.s);
+
+       } else if (res == 2) {
+               if (prom_gauge_reset(&s_name, &l1, &l2, NULL)) {
+                       LM_ERR("Cannot reset gauge: %.*s (%.*s, %.*s)\n", s_name.len, s_name.s,
+                                  l1.len, l1.s,
+                                  l2.len, l2.s);
+                       rpc->fault(ct, 500, "Failed to reset gauge: %.*s (%.*s, %.*s)",
+                                          s_name.len, s_name.s,
+                                          l1.len, l1.s,
+                                          l2.len, l2.s
+                               );
+                       return;
+               }
+               LM_DBG("Gauge reset: %.*s (%.*s, %.*s)\n", s_name.len, s_name.s,
+                          l1.len, l1.s,
+                          l2.len, l2.s);
+
+       } else if (res == 3) {
+               if (prom_gauge_reset(&s_name, &l1, &l2, &l3)) {
+                       LM_ERR("Cannot reset gauge: %.*s (%.*s, %.*s, %.*s)\n", s_name.len, s_name.s,
+                                  l1.len, l1.s,
+                                  l2.len, l2.s,
+                                  l3.len, l3.s
+                               );
+                       rpc->fault(ct, 500, "Failed to reset gauge: %.*s (%.*s, %.*s, %.*s)",
+                                          s_name.len, s_name.s,
+                                          l1.len, l1.s,
+                                          l2.len, l2.s,
+                                          l3.len, l3.s
+                               );
+                       return;
+               }
+               LM_DBG("Gauge reset: %.*s (%.*s, %.*s, %.*s)\n", s_name.len, s_name.s,
+                          l1.len, l1.s,
+                          l2.len, l2.s,
+                          l3.len, l3.s
+                       );
+
+       } else {
+               LM_ERR("Strange return value: %d\n", res);
+               rpc->fault(ct, 500, "Strange return value: %d", res);
+
+       } /* if res == 0 */
+               
+       return;
+}
+
+static void rpc_prom_gauge_set(rpc_t *rpc, void *ct)
+{
+       str s_name;
+
+       if (rpc->scan(ct, "S", &s_name) < 1) {
+               rpc->fault(ct, 400, "required gauge identifier");
+               return;
+       }
+
+       if (s_name.len == 0 || s_name.s == NULL) {
+               rpc->fault(ct, 400, "invalid gauge identifier");
+               return;
+       }
+
+       double number;
+       if (rpc->scan(ct, "f", &number) < 1) {
+               rpc->fault(ct, 400, "required number argument");
+               return;
+       }
+
+       str l1, l2, l3;
+       int res;
+       res = rpc->scan(ct, "*SSS", &l1, &l2, &l3);
+       if (res == 0) {
+               /* No labels */
+               if (prom_gauge_set(&s_name, number, NULL, NULL, NULL)) {
+                       LM_ERR("Cannot assign %f to gauge %.*s\n", number, s_name.len, s_name.s);
+                       rpc->fault(ct, 500, "Failed to assign %f gauge: %.*s", number,
+                                          s_name.len, s_name.s);
+                       return;
+               }
+               LM_DBG("Assigned %f to gauge (%.*s)\n", number, s_name.len, s_name.s);
+               
+       } else if (res == 1) {
+               if (prom_gauge_set(&s_name, number, &l1, NULL, NULL)) {
+                       LM_ERR("Cannot assign %f to gauge %.*s (%.*s)\n", number, s_name.len, s_name.s,
+                                  l1.len, l1.s);
+                       rpc->fault(ct, 500, "Failed to assign %f to gauge: %.*s (%.*s)",
+                                          number, s_name.len, s_name.s,
+                                          l1.len, l1.s);
+                       return;
+               }
+               LM_DBG("Assigned %f to gauge: %.*s (%.*s)\n", number, s_name.len, s_name.s,
+                          l1.len, l1.s);
+
+       } else if (res == 2) {
+               if (prom_gauge_set(&s_name, number, &l1, &l2, NULL)) {
+                       LM_ERR("Cannot assign %f to gauge: %.*s (%.*s, %.*s)\n", number,
+                                  s_name.len, s_name.s,
+                                  l1.len, l1.s,
+                                  l2.len, l2.s);
+                       rpc->fault(ct, 500, "Failed to assign %f to gauge: %.*s (%.*s, %.*s)",
+                                          number, s_name.len, s_name.s,
+                                          l1.len, l1.s,
+                                          l2.len, l2.s
+                               );
+                       return;
+               }
+               LM_DBG("Assigned %f to gauge: %.*s (%.*s, %.*s)\n", number, s_name.len, s_name.s,
+                          l1.len, l1.s,
+                          l2.len, l2.s);
+
+       } else if (res == 3) {
+               if (prom_gauge_set(&s_name, number, &l1, &l2, &l3)) {
+                       LM_ERR("Cannot assign %f to gauge: %.*s (%.*s, %.*s, %.*s)\n",
+                                  number, s_name.len, s_name.s,
+                                  l1.len, l1.s,
+                                  l2.len, l2.s,
+                                  l3.len, l3.s
+                               );
+                       rpc->fault(ct, 500, "Failed to assign %f to gauge: %.*s (%.*s, %.*s, %.*s)",
+                                          number, s_name.len, s_name.s,
+                                          l1.len, l1.s,
+                                          l2.len, l2.s,
+                                          l3.len, l3.s
+                               );
+                       return;
+               }
+               LM_DBG("Assigned %f to gauge: %.*s (%.*s, %.*s, %.*s)\n",
+                          number, s_name.len, s_name.s,
+                          l1.len, l1.s,
+                          l2.len, l2.s,
+                          l3.len, l3.s
+                       );
+
+       } else {
+               LM_ERR("Strange return value: %d\n", res);
+               rpc->fault(ct, 500, "Strange return value: %d", res);
+
+       } /* if res == 0 */
+
+       return;
+}
+
+static void rpc_prom_metric_list_print(rpc_t *rpc, void *ct)
+{
+       /* We reuse ctx->reply for the occasion. */
+       if (init_xhttp_prom_reply(&ctx) < 0) {
+               goto clean;
+       }
+
+       if (prom_metric_list_print(&ctx)) {
+               LM_ERR("Cannot print a list of metrics\n");
+               goto clean;
+       }
+
+       /* Convert to zero terminated string. */
+       struct xhttp_prom_reply* reply;
+       reply = &(ctx.reply);
+       reply->body.s[reply->body.len] = '\0';
+
+       /* Print content of reply buffer. */
+       if (rpc->rpl_printf(ct, reply->body.s) < 0) {
+               LM_ERR("Error printing RPC response\n");
+               goto clean;
+       }
+       
+clean:
+
+       xhttp_prom_reply_free(&ctx);
+       
+       return;
+}
+
+static const char* rpc_prom_counter_reset_doc[2] = {
+       "Reset a counter based on its identifier",
+       0
+};
+
+static const char* rpc_prom_counter_inc_doc[2] = {
+       "Add a number (greater or equal to zero) to a counter based on its identifier",
+       0
+};
+
+static const char* rpc_prom_gauge_reset_doc[2] = {
+       "Reset a gauge based on its identifier",
+       0
+};
+
+static const char* rpc_prom_gauge_set_doc[2] = {
+       "Set a gauge to a number based on its identifier",
+       0
+};
+
+static const char* rpc_prom_metric_list_print_doc[2] = {
+       "Print a list showing all user defined metrics",
+       0
+};
+
+static rpc_export_t rpc_cmds[] = {
+       {"xhttp_prom.counter_reset", rpc_prom_counter_reset, rpc_prom_counter_reset_doc, 0},
+       {"xhttp_prom.counter_inc", rpc_prom_counter_inc, rpc_prom_counter_inc_doc, 0},
+       {"xhttp_prom.gauge_reset", rpc_prom_gauge_reset, rpc_prom_gauge_reset_doc, 0},
+       {"xhttp_prom.gauge_set", rpc_prom_gauge_set, rpc_prom_gauge_set_doc, 0},
+       {"xhttp_prom.metric_list_print", rpc_prom_metric_list_print, rpc_prom_metric_list_print_doc, 0},
+       {0, 0, 0, 0}
+};
diff --git a/src/modules/xhttp_prom/xhttp_prom.h b/src/modules/xhttp_prom/xhttp_prom.h
new file mode 100644 (file)
index 0000000..982b4e9
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2012 VoIP Embedded, Inc.
+ *
+ * Copyright (C) 2019 Vicente Hernando (Sonoc)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+/*!
+ * \file
+ * \brief XHTTP_PROM :: 
+ * \ingroup xhttp_prom
+ * Module: \ref xhttp_prom
+ */
+
+#ifndef _XHTTP_PROM_H
+#define _XHTTP_PROM_H
+
+#include "../../core/str.h"
+#include "../../core/parser/msg_parser.h"
+
+
+#define ERROR_REASON_BUF_LEN 1024
+#define PRINT_VALUE_BUF_LEN 256
+
+
+
+/** Representation of the xhttp_prom reply being constructed.
+ *
+ * This data structure describes the xhttp_prom reply that is being constructed
+ * and will be sent to the client.
+ */
+struct xhttp_prom_reply {
+       int code;       /**< Reply code which indicates the type of the reply */
+       str reason;     /**< Reason phrase text which provides human-readable
+                        * description that augments the reply code */
+       str body;       /**< The xhttp_prom http body built so far */
+       str buf;        /**< The memory buffer allocated for the reply, this is
+                        * where the body attribute of the structure points to */
+};
+
+
+/** The context of the xhttp_prom request being processed.
+ *
+ * This is the data structure that contains all data related to the xhttp_prom
+ * request being processed, such as the reply code and reason, data to be sent
+ * to the client in the reply, and so on.
+ *
+ * There is always one context per xhttp_prom request.
+ */
+typedef struct prom_ctx {
+       sip_msg_t* msg;                 /**< The SIP/HTTP received message. */
+       struct xhttp_prom_reply reply;  /**< xhttp_prom reply to be sent to the client */
+       int reply_sent;
+} prom_ctx_t;
+
+
+#endif /* _XHTTP_PROM_H */
+