{"id":1074,"date":"2021-12-17T00:00:00","date_gmt":"2021-12-16T11:00:00","guid":{"rendered":"https:\/\/blog.wiseowls.co.nz\/?p=1074"},"modified":"2026-03-08T00:48:03","modified_gmt":"2026-03-07T11:48:03","slug":"writing-prometheus-exporters-the-lazy-dev-way","status":"publish","type":"post","link":"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/","title":{"rendered":"Writing Prometheus exporters &#8211; the Lazy Dev way"},"content":{"rendered":"<p><a href=\"mailto:info@example.com?&amp;subject=&amp;body=https:\/\/codetrain.io\/why-the-csharp-record-type-is-important%3Fsource%3Demail\"><\/a>This article is part of the&nbsp;<a href=\"https:\/\/www.csadvent.christmas\/\">C# Advent Series<\/a>. Christmas has a special place in our hearts and this event is also a wonderful way to help build up the C# community. Do check out awesome content from other authors!<\/p>\n<p>There&#8217;s a couple of things about Christmas in Southern Hemisphere that tends to hit us pretty hard each year: first, the fact that it is summer and it&#8217;s scorching hot outside. And second &#8211; is a customary closedown of almost all businesses (which probably started as response to the first point). Some businesses, however, keep boxing on. <\/p>\n<p>One of our clients is into cryptocurrency mining and they could not care less about staff wanting time off to spend with family. Their only workforce are GPUs, and these devices can work 24\/7. However, with temperatures creeping up, efficiency takes a hit. Also, other sad things can happen:<\/p>\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"611\" src=\"https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/11\/t-rex-miner-burnt-GPU-1024x611.jpg\" alt=\"Burnt GPU card from cryptocurrency mining rig overheating\" class=\"wp-image-1096\" srcset=\"https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/11\/t-rex-miner-burnt-GPU-1024x611.jpg 1024w, https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/11\/t-rex-miner-burnt-GPU-300x179.jpg 300w, https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/11\/t-rex-miner-burnt-GPU-768x458.jpg 768w, https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/11\/t-rex-miner-burnt-GPU-1200x716.jpg 1200w, https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/11\/t-rex-miner-burnt-GPU.jpg 1210w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/figure>\n<h2 class=\"wp-block-heading\">Solution design<\/h2>\n<p>Our first suggestion was to use our trusty <a href=\"https:\/\/blog.wiseowls.co.nz\/index.php\/2019\/09\/13\/monitoring-sql-server-setting-up-elkg\/\">ELK+G<\/a> and poll extra data from <a href=\"https:\/\/developer.nvidia.com\/nvidia-system-management-interface\">NVIDIA SMI<\/a> tool, but we soon figured out that this problem has already been solved for us. Mining software nowadays got extremely sophisticated (and obfuscated) &#8211; it now comes with own webserver and <a href=\"https:\/\/github.com\/trexminer\/T-Rex\/wiki\/API\">API<\/a>. So, we simplified a bit:<\/p>\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"571\" src=\"https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/11\/t-rex-miner-solution-design.drawio-1024x571.png\" alt=\"Solution design showing T-Rex miner API feeding into Prometheus exporter and Grafana\" class=\"wp-image-1075\" srcset=\"https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/11\/t-rex-miner-solution-design.drawio-1024x571.png 1024w, https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/11\/t-rex-miner-solution-design.drawio-300x167.png 300w, https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/11\/t-rex-miner-solution-design.drawio-768x428.png 768w, https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/11\/t-rex-miner-solution-design.drawio-1200x669.png 1200w, https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/11\/t-rex-miner-solution-design.drawio.png 1270w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/figure>\n<p>All we have to do here would be to stand up an exporter and set up a few dashboards. Easy.<\/p>\n<h2 class=\"wp-block-heading\">Hosted Services<\/h2>\n<p>We essentially need to run two services: poll underlying API and expose metrics in Prometheus-friendly format. We felt <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/host\/generic-host?view=aspnetcore-3.1\">.NET Core Generic host<\/a> infrastructure would fit very well here. It allows us to bootstrap an app, add Hosted Services and leave plumbing to Docker. The program ended up looking like so:<\/p>\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">class Program\n    {\n        private static async Task Main(string[] args)\n        {\n            using IHost host = CreatHostBuilder(args).Build();\n            \n            await host.RunAsync();\n        }\n        static IHostBuilder CreateHostBuilder(string[] args) =&gt;\n            Host.CreateDefaultBuilder(args)\n                .ConfigureAppConfiguration((configuration) =&gt;\n                {\n                    configuration.AddEnvironmentVariables(\"TREX\")\n; \/\/ can add more sources such as command line\n                })\n                .ConfigureServices(c =&gt;\n                {\n                    c.AddSingleton&lt;MetricCollection&gt;(); \/\/ This is where we will keep all metrics state. hence singleton\n                    c.AddHostedService&lt;PrometheusExporter&gt;(); \/\/ exposes MetricCollection\n                    c.AddHostedService&lt;TRexPoller&gt;(); \/\/ periodically GETs status and updates MetricCollection\n                });\n    }<\/code><\/pre>\n<h2 class=\"wp-block-heading\">Defining services<\/h2>\n<p>The two parts of our applicatgion are <code>TRexPoller<\/code> and <code>PrometheusExporter<\/code>. Writing both is trivial and we won&#8217;t spend much time on the code there. Feel free to <a href=\"https:\/\/github.com\/tkhadimullin\/trex-exporter\">check it out<\/a> on GitHub. The point to make here is it has never been easier to focus on business logic and leave heavy lifting to respective NuGet packages. <\/p>\n<h2 class=\"wp-block-heading\">Crafting the models<\/h2>\n<p>The most important part of our application is of course telemetry. We grabbed a sample json response from the API and used an online tool to convert that into C# classes:<\/p>\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">\/\/ generated code looks like this. A set of POCOs with each property decorated with JsonProperty that maps to api response\npublic partial class Gpu\n{\n    [JsonProperty(\"device_id\")]\n    public int DeviceId { get; set; }\n    [JsonProperty(\"hashrate\")]\n    public int Hashrate { get; set; }\n    [JsonProperty(\"hashrate_day\")]\n    public int HashrateDay { get; set; }\n    [JsonProperty(\"hashrate_hour\")]\n    public int HashrateHour { get; set; }\n...\n}<\/code><\/pre>\n<p>Now we need to define metrics that <a href=\"https:\/\/github.com\/prometheus-net\/prometheus-net#quick-start\">Prometheus.Net<\/a> can later discover and serve up: <\/p>\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">\/\/ example taken from https:\/\/github.com\/prometheus-net\/prometheus-net#quick-start\nprivate static readonly Counter ProcessedJobCount = Metrics\n    .CreateCounter(\"myapp_jobs_processed_total\", \"Number of processed jobs.\");\n...\nProcessJob();\nProcessedJobCount.Inc();<\/code><\/pre>\n<h2 class=\"wp-block-heading\">Turning on lazy mode<\/h2>\n<p>This is where we&#8217;ve got so inspired by our &#8220;low code&#8221; solution that we didn&#8217;t want to get down to hand-crafting a bunch of class fields to describe every single value the API serves. Luckily, C#9 has a new feature just for us: <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/csharp\/whats-new\/csharp-9#support-for-code-generators\">Source Code Generators<\/a> to the rescue! We&#8217;ve <a href=\"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/03\/22\/calling-wcf-services-from-net-core-clients\/\">covered the basic setup<\/a> before, so we&#8217;ll skip this part here and move on to the Christmas magic part.<\/p>\n<h2 class=\"wp-block-heading\">Let Code Generators do the work for us<\/h2>\n<p>Before we hand everything over to robots, we need to set some basic rules to control the process. Custom attributes looked like a sensible way to keep all configuration local with the model POCOs:<\/p>\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">[AddInstrumentation(\"gpus\")] \/\/ the first attribute prompts the generator to loop through the properties and search for metrics \npublic partial class Gpu\n{\n    [JsonProperty(\"device_id\")]\n    public int DeviceId { get; set; }\n    [JsonProperty(\"hashrate\")]\n    \/*\n     * the second attribute controls which type the metric will have as well as what labels we want to store with it.\n     * In this example, it's a Gauge with gpu_id, vendor and name being labels for grouping in Prometheus\n     *\/\n    [Metric(\"Gauge\", \"gpu_id\", \"vendor\", \"name\")]\n    public int Hashrate { get; set; }\n    [JsonProperty(\"hashrate_day\")]\n    [Metric(\"Gauge\", \"gpu_id\", \"vendor\", \"name\")]\n    public int HashrateDay { get; set; }\n    [JsonProperty(\"hashrate_hour\")]\n    [Metric(\"Gauge\", \"gpu_id\", \"vendor\", \"name\")]\n    public int HashrateHour { get; set; }\n<\/code><\/pre>\n<p>Finally, the generator itself hooks into <code>ClassDeclarationSyntax<\/code> and looks for well-known attributes:<\/p>\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">public void OnVisitSyntaxNode(SyntaxNode syntaxNode)\n    {\n        if (syntaxNode is ClassDeclarationSyntax cds &amp;&amp; cds.AttributeLists\n            .SelectMany(al =&gt; al.Attributes)\n            .Any(a =&gt; (a.Name as IdentifierNameSyntax)?.Identifier.ValueText == \"AddInstrumentation\"))\n        {\n            ClassesToProcess.Add(cds);\n        }\n    }\n<\/code><\/pre>\n<p>Once we&#8217;ve got our list, we loop through each property and generate a dictionary of <code>Collector<\/code> objects.<\/p>\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">var text = new StringBuilder(@\"public static Dictionary&lt;string, Collector&gt; GetMetrics(string prefix)\n    {\n        var result = new Dictionary&lt;string, Collector&gt;\n        {\").AppendLine();\nforeach (PropertyDeclarationSyntax p in properties)\n{\n    var jsonPropertyAttr = p.GetAttr(\"JsonProperty\");\n    var metricAttr = p.GetAttr(\"Metric\");\n    if (metricAttr == null) continue;\n    var propName = jsonPropertyAttr.GetFirstParameterValue();\n    var metricName = metricAttr.GetFirstParameterValue(); \/\/ determine metric type\n    if (metricAttr.ArgumentList.Arguments.Count &gt; 1)\n    {\n        var labels = metricAttr.GetTailParameterValues(); \/\/ if we have extra labels to process - here's our chance \n        text.AppendLine(\n            $\"{{$\\\"{{prefix}}{attrPrefix}_{propName}\\\", Metrics.Create{metricName}($\\\"{{prefix}}{attrPrefix}_{propName}\\\", \\\"{propName}\\\", {commonLabels}, {labels}) }},\");\n    }\n    else\n    {\n        text.AppendLine(\n            $\"{{$\\\"{{prefix}}{attrPrefix}_{propName}\\\", Metrics.Create{metricName}($\\\"{{prefix}}{attrPrefix}_{propName}\\\", \\\"{propName}\\\", {commonLabels}) }},\");\n    }\n}\ntext.AppendLine(@\"};\n                return result;\n            }\");<\/code><\/pre>\n<p>In parallel to defining storage for metrics, we also need to generate code that will update values as soon as we&#8217;ve heard back from the API:<\/p>\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">private StringBuilder UpdateMetrics(List&lt;MemberDeclarationSyntax&gt; properties, SyntaxToken classToProcess, string attrPrefix)\n{\n    var text = new StringBuilder($\"public static void UpdateMetrics(string prefix, Dictionary&lt;string, Collector&gt; metrics, {classToProcess} data, string host, string slot, string algo, List&lt;string&gt; extraLabels = null) {{\");\n    text.AppendLine();\n    text.AppendLine(@\"if(extraLabels == null) { \n                            extraLabels = new List&lt;string&gt; {host, slot, algo};\n                        }\n                        else {\n                            extraLabels.Insert(0, algo);\n                            extraLabels.Insert(0, slot);\n                            extraLabels.Insert(0, host);\n                        }\");\n    foreach (PropertyDeclarationSyntax p in properties)\n    {\n        var jsonPropertyAttr = p.GetAttr(\"JsonProperty\");\n        var metricAttr = p.GetAttr(\"Metric\");\n        if (metricAttr == null) continue;\n        var propName = jsonPropertyAttr.GetFirstParameterValue();\n        var metricName = metricAttr.GetFirstParameterValue();\n        var newValue = $\"data.{p.Identifier.ValueText}\";\n        text.Append(\n            $\"(metrics[$\\\"{{prefix}}{attrPrefix}_{propName}\\\"] as {metricName}).WithLabels(extraLabels.ToArray())\");\n        switch (metricName)\n        {\n            case \"Counter\": text.AppendLine($\".IncTo({newValue});\"); break;\n            case \"Gauge\": text.AppendLine($\".Set({newValue});\"); break;\n        }\n    }\n    text.AppendLine(\"}\").AppendLine();\n    return text;\n}<\/code><\/pre>\n<h2 class=\"wp-block-heading\">Bringing it all together with  MetricCollection <\/h2>\n<p>Finally, we can use the generated code to bootstrap metrics on per-model basis and ensure we correctly handle updates:<\/p>\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">internal class MetricCollection\n{\n    private readonly Dictionary&lt;string, Collector&gt; _metrics;\n    private readonly string _prefix;\n    private readonly string _host;\n    public MetricCollection(IConfiguration configuration)\n    {\n        _prefix = configuration.GetValue&lt;string&gt;(\"exporterPrefix\", \"trex\");\n        _metrics = new Dictionary&lt;string, Collector&gt;();\n        \/\/ this is where declaring particl classes and generating extra methods makes for seamless development experience\n        foreach (var (key, value) in TRexResponse.GetMetrics(_prefix)) _metrics.Add(key, value);\n        foreach (var (key, value) in DualStat.GetMetrics(_prefix)) _metrics.Add(key, value);\n        foreach (var (key, value) in Gpu.GetMetrics(_prefix)) _metrics.Add(key, value);\n        foreach (var (key, value) in Shares.GetMetrics(_prefix)) _metrics.Add(key, value);\n    }\n    public void Update(TRexResponse data)\n    {\n        TRexResponse.UpdateMetrics(_prefix, _metrics, data, _host, \"main\", data.Algorithm);\n        DualStat.UpdateMetrics(_prefix, _metrics, data.DualStat, _host, \"dual\", data.DualStat.Algorithm);\n        \n        foreach (var dataGpu in data.Gpus)\n        {\n            Gpu.UpdateMetrics(_prefix, _metrics, dataGpu, _host, \"main\", data.Algorithm, new List&lt;string&gt;\n            {\n                dataGpu.DeviceId.ToString(),\n                dataGpu.Vendor,\n                dataGpu.Name\n            });\n            Shares.UpdateMetrics(_prefix, _metrics, dataGpu.Shares, _host, \"main\", data.Algorithm, new List&lt;string&gt;\n            {\n                dataGpu.GpuId.ToString(),\n                dataGpu.Vendor,\n                dataGpu.Name\n            });\n        }\n    }\n}<\/code><\/pre>\n<h2 class=\"wp-block-heading\">Peeking into generated code<\/h2>\n<p>Just to make sure we&#8217;re on the right track, we looked at generated code. It ain&#8217;t pretty but it&#8217;s honest work:<\/p>\n<pre class=\"wp-block-code\"><code class=\"\">public partial class Shares {\npublic static Dictionary&lt;string, Collector&gt; GetMetrics(string prefix)\n                {\n                    var result = new Dictionary&lt;string, Collector&gt;\n                    {\n{$\"{prefix}_shares_accepted_count\", Metrics.CreateCounter($\"{prefix}_shares_accepted_count\", \"accepted_count\", \"host\", \"slot\", \"algo\", \"gpu_id\", \"vendor\", \"name\") },\n{$\"{prefix}_shares_invalid_count\", Metrics.CreateCounter($\"{prefix}_shares_invalid_count\", \"invalid_count\", \"host\", \"slot\", \"algo\", \"gpu_id\", \"vendor\", \"name\") },\n{$\"{prefix}_shares_last_share_diff\", Metrics.CreateGauge($\"{prefix}_shares_last_share_diff\", \"last_share_diff\", \"host\", \"slot\", \"algo\", \"gpu_id\", \"vendor\", \"name\") },\n...\n};\n                            return result;\n                        }\npublic static void UpdateMetrics(string prefix, Dictionary&lt;string, Collector&gt; metrics, Shares data, string host, string slot, string algo, List&lt;string&gt; extraLabels = null) {\nif(extraLabels == null) { \n                                    extraLabels = new List&lt;string&gt; {host, slot, algo};\n                                }\n                                else {\n                                    extraLabels.Insert(0, algo);\n                                    extraLabels.Insert(0, slot);\n                                    extraLabels.Insert(0, host);\n                                }\n(metrics[$\"{prefix}_shares_accepted_count\"] as Counter).WithLabels(extraLabels.ToArray()).IncTo(data.AcceptedCount);\n(metrics[$\"{prefix}_shares_invalid_count\"] as Counter).WithLabels(extraLabels.ToArray()).IncTo(data.InvalidCount);\n(metrics[$\"{prefix}_shares_last_share_diff\"] as Gauge).WithLabels(extraLabels.ToArray()).Set(data.LastShareDiff);\n...\n}\n}<\/code><\/pre>\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n<p>This example barely scratches the surface of what&#8217;s possible with this feature. Source code generators are extremely helpful when we deal with tedious and repetitive development tasks. It also helps reduce maintenance overheads by enabling us to switch to declarative approach. I&#8217;m sure we will see more projects coming up where this feature will become central to the solution.<\/p>\n<p>If not already, do check out <a href=\"https:\/\/github.com\/tkhadimullin\/trex-exporter\">the source code in GitHub<\/a>. And as for us, we would like to sign off with warmest greetings of this festive season&nbsp;and best wishes for&nbsp;happiness in the New Year.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This article is part of the&nbsp;C# Advent Series. Christmas has a special place in our hearts and this event is also a wonderful way to help build up the C# community. Do check out awesome content from other authors! There&#8217;s a couple of things about Christmas in Southern Hemisphere that tends to hit us pretty &hellip; <a href=\"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Writing Prometheus exporters &#8211; the Lazy Dev way&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":1097,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[11],"tags":[12,38,29],"class_list":["post-1074","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dev","tag-c","tag-monitoring","tag-webapi"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Writing Prometheus exporters - the Lazy Dev way - Timur and associates<\/title>\n<meta name=\"description\" content=\"Sometimes we have an API and no apparent way to convert it to our monitoring platform of choice. In this post we&#039;ll explore a way to solve it\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Writing Prometheus exporters - the Lazy Dev way - Timur and associates\" \/>\n<meta property=\"og:description\" content=\"Sometimes we have an API and no apparent way to convert it to our monitoring platform of choice. In this post we&#039;ll explore a way to solve it\" \/>\n<meta property=\"og:url\" content=\"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/\" \/>\n<meta property=\"og:site_name\" content=\"Timur and associates\" \/>\n<meta property=\"article:published_time\" content=\"2021-12-16T11:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-03-07T11:48:03+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/12\/monitoring-stock.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"624\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"timur\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@TimurKh\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/index.php\\\/2021\\\/12\\\/17\\\/writing-prometheus-exporters-the-lazy-dev-way\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/index.php\\\/2021\\\/12\\\/17\\\/writing-prometheus-exporters-the-lazy-dev-way\\\/\"},\"author\":{\"name\":\"timur\",\"@id\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/#\\\/schema\\\/person\\\/34d0ed30d573b5bc317ea990bd2e0c59\"},\"headline\":\"Writing Prometheus exporters &#8211; the Lazy Dev way\",\"datePublished\":\"2021-12-16T11:00:00+00:00\",\"dateModified\":\"2026-03-07T11:48:03+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/index.php\\\/2021\\\/12\\\/17\\\/writing-prometheus-exporters-the-lazy-dev-way\\\/\"},\"wordCount\":716,\"commentCount\":0,\"image\":{\"@id\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/index.php\\\/2021\\\/12\\\/17\\\/writing-prometheus-exporters-the-lazy-dev-way\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/wp-content\\\/uploads\\\/2021\\\/12\\\/monitoring-stock.jpg\",\"keywords\":[\"c#\",\"monitoring\",\"webapi\"],\"articleSection\":[\"Development\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/blog.wiseowls.co.nz\\\/index.php\\\/2021\\\/12\\\/17\\\/writing-prometheus-exporters-the-lazy-dev-way\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/index.php\\\/2021\\\/12\\\/17\\\/writing-prometheus-exporters-the-lazy-dev-way\\\/\",\"url\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/index.php\\\/2021\\\/12\\\/17\\\/writing-prometheus-exporters-the-lazy-dev-way\\\/\",\"name\":\"Writing Prometheus exporters - the Lazy Dev way - Timur and associates\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/index.php\\\/2021\\\/12\\\/17\\\/writing-prometheus-exporters-the-lazy-dev-way\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/index.php\\\/2021\\\/12\\\/17\\\/writing-prometheus-exporters-the-lazy-dev-way\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/wp-content\\\/uploads\\\/2021\\\/12\\\/monitoring-stock.jpg\",\"datePublished\":\"2021-12-16T11:00:00+00:00\",\"dateModified\":\"2026-03-07T11:48:03+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/#\\\/schema\\\/person\\\/34d0ed30d573b5bc317ea990bd2e0c59\"},\"description\":\"Sometimes we have an API and no apparent way to convert it to our monitoring platform of choice. In this post we'll explore a way to solve it\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/index.php\\\/2021\\\/12\\\/17\\\/writing-prometheus-exporters-the-lazy-dev-way\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/blog.wiseowls.co.nz\\\/index.php\\\/2021\\\/12\\\/17\\\/writing-prometheus-exporters-the-lazy-dev-way\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/index.php\\\/2021\\\/12\\\/17\\\/writing-prometheus-exporters-the-lazy-dev-way\\\/#primaryimage\",\"url\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/wp-content\\\/uploads\\\/2021\\\/12\\\/monitoring-stock.jpg\",\"contentUrl\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/wp-content\\\/uploads\\\/2021\\\/12\\\/monitoring-stock.jpg\",\"width\":1920,\"height\":624},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/index.php\\\/2021\\\/12\\\/17\\\/writing-prometheus-exporters-the-lazy-dev-way\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Writing Prometheus exporters &#8211; the Lazy Dev way\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/#website\",\"url\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/\",\"name\":\"Timur and associates\",\"description\":\"Notes of an IT contractor\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/blog.wiseowls.co.nz\\\/#\\\/schema\\\/person\\\/34d0ed30d573b5bc317ea990bd2e0c59\",\"name\":\"timur\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/23d55e17d4f0990ee4d12bc6e5dcfb58a292934efd62a185756876379e780b16?s=96&r=pg\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/23d55e17d4f0990ee4d12bc6e5dcfb58a292934efd62a185756876379e780b16?s=96&r=pg\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/23d55e17d4f0990ee4d12bc6e5dcfb58a292934efd62a185756876379e780b16?s=96&r=pg\",\"caption\":\"timur\"},\"sameAs\":[\"https:\\\/\\\/x.com\\\/TimurKh\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Writing Prometheus exporters - the Lazy Dev way - Timur and associates","description":"Sometimes we have an API and no apparent way to convert it to our monitoring platform of choice. In this post we'll explore a way to solve it","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/","og_locale":"en_US","og_type":"article","og_title":"Writing Prometheus exporters - the Lazy Dev way - Timur and associates","og_description":"Sometimes we have an API and no apparent way to convert it to our monitoring platform of choice. In this post we'll explore a way to solve it","og_url":"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/","og_site_name":"Timur and associates","article_published_time":"2021-12-16T11:00:00+00:00","article_modified_time":"2026-03-07T11:48:03+00:00","og_image":[{"width":1920,"height":624,"url":"https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/12\/monitoring-stock.jpg","type":"image\/jpeg"}],"author":"timur","twitter_card":"summary_large_image","twitter_creator":"@TimurKh","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/#article","isPartOf":{"@id":"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/"},"author":{"name":"timur","@id":"https:\/\/blog.wiseowls.co.nz\/#\/schema\/person\/34d0ed30d573b5bc317ea990bd2e0c59"},"headline":"Writing Prometheus exporters &#8211; the Lazy Dev way","datePublished":"2021-12-16T11:00:00+00:00","dateModified":"2026-03-07T11:48:03+00:00","mainEntityOfPage":{"@id":"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/"},"wordCount":716,"commentCount":0,"image":{"@id":"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/#primaryimage"},"thumbnailUrl":"https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/12\/monitoring-stock.jpg","keywords":["c#","monitoring","webapi"],"articleSection":["Development"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/","url":"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/","name":"Writing Prometheus exporters - the Lazy Dev way - Timur and associates","isPartOf":{"@id":"https:\/\/blog.wiseowls.co.nz\/#website"},"primaryImageOfPage":{"@id":"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/#primaryimage"},"image":{"@id":"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/#primaryimage"},"thumbnailUrl":"https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/12\/monitoring-stock.jpg","datePublished":"2021-12-16T11:00:00+00:00","dateModified":"2026-03-07T11:48:03+00:00","author":{"@id":"https:\/\/blog.wiseowls.co.nz\/#\/schema\/person\/34d0ed30d573b5bc317ea990bd2e0c59"},"description":"Sometimes we have an API and no apparent way to convert it to our monitoring platform of choice. In this post we'll explore a way to solve it","breadcrumb":{"@id":"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/#primaryimage","url":"https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/12\/monitoring-stock.jpg","contentUrl":"https:\/\/blog.wiseowls.co.nz\/wp-content\/uploads\/2021\/12\/monitoring-stock.jpg","width":1920,"height":624},{"@type":"BreadcrumbList","@id":"https:\/\/blog.wiseowls.co.nz\/index.php\/2021\/12\/17\/writing-prometheus-exporters-the-lazy-dev-way\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/blog.wiseowls.co.nz\/"},{"@type":"ListItem","position":2,"name":"Writing Prometheus exporters &#8211; the Lazy Dev way"}]},{"@type":"WebSite","@id":"https:\/\/blog.wiseowls.co.nz\/#website","url":"https:\/\/blog.wiseowls.co.nz\/","name":"Timur and associates","description":"Notes of an IT contractor","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blog.wiseowls.co.nz\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/blog.wiseowls.co.nz\/#\/schema\/person\/34d0ed30d573b5bc317ea990bd2e0c59","name":"timur","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/23d55e17d4f0990ee4d12bc6e5dcfb58a292934efd62a185756876379e780b16?s=96&r=pg","url":"https:\/\/secure.gravatar.com\/avatar\/23d55e17d4f0990ee4d12bc6e5dcfb58a292934efd62a185756876379e780b16?s=96&r=pg","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/23d55e17d4f0990ee4d12bc6e5dcfb58a292934efd62a185756876379e780b16?s=96&r=pg","caption":"timur"},"sameAs":["https:\/\/x.com\/TimurKh"]}]}},"_links":{"self":[{"href":"https:\/\/blog.wiseowls.co.nz\/index.php\/wp-json\/wp\/v2\/posts\/1074","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.wiseowls.co.nz\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.wiseowls.co.nz\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.wiseowls.co.nz\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.wiseowls.co.nz\/index.php\/wp-json\/wp\/v2\/comments?post=1074"}],"version-history":[{"count":27,"href":"https:\/\/blog.wiseowls.co.nz\/index.php\/wp-json\/wp\/v2\/posts\/1074\/revisions"}],"predecessor-version":[{"id":1347,"href":"https:\/\/blog.wiseowls.co.nz\/index.php\/wp-json\/wp\/v2\/posts\/1074\/revisions\/1347"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.wiseowls.co.nz\/index.php\/wp-json\/wp\/v2\/media\/1097"}],"wp:attachment":[{"href":"https:\/\/blog.wiseowls.co.nz\/index.php\/wp-json\/wp\/v2\/media?parent=1074"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.wiseowls.co.nz\/index.php\/wp-json\/wp\/v2\/categories?post=1074"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.wiseowls.co.nz\/index.php\/wp-json\/wp\/v2\/tags?post=1074"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}