浏览代码

Merge pull request #1976 from kamailio/vhernando/xhttp_prom_branch

xhttp_prom: module to generate Prometheus metrics.
Daniel-Constantin Mierla 6 年之前
父节点
当前提交
1c3ab78a6e

+ 9 - 0
src/modules/xhttp_prom/Makefile

@@ -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

+ 4 - 0
src/modules/xhttp_prom/doc/Makefile

@@ -0,0 +1,4 @@
+docs = xhttp_prom.xml
+
+docbook_dir = ../../../../doc/docbook
+include $(docbook_dir)/Makefile.module

+ 41 - 0
src/modules/xhttp_prom/doc/xhttp_prom.xml

@@ -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>[email protected]</email>
+			</author>
+			<editor>
+				<firstname>Vicente</firstname>
+				<surname>Hernando</surname>
+				<email>[email protected]</email>
+			</editor>
+			<author>
+			    <firstname>Javier</firstname>
+				<surname>Gallart</surname>
+				<email>[email protected]</email>
+			</author>
+		</authorgroup>
+		<copyright>
+			<year>2019</year>
+			<holder>www.sonoc.io</holder>
+		</copyright>
+	</bookinfo>
+	<toc></toc>
+
+	<xi:include href="xhttp_prom_admin.xml"/>
+
+</book>

+ 728 - 0
src/modules/xhttp_prom/doc/xhttp_prom_admin.xml

@@ -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>	

+ 280 - 0
src/modules/xhttp_prom/prom.c

@@ -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 */
+

+ 56 - 0
src/modules/xhttp_prom/prom.h

@@ -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_

+ 1361 - 0
src/modules/xhttp_prom/prom_metric.c

@@ -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;
+}
+

+ 78 - 0
src/modules/xhttp_prom/prom_metric.h

@@ -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_

+ 2092 - 0
src/modules/xhttp_prom/xhttp_prom.c

@@ -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}
+};

+ 74 - 0
src/modules/xhttp_prom/xhttp_prom.h

@@ -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 */
+