├── .gitignore ├── Assets └── Logo │ ├── logo-1024.png │ ├── logo-128.png │ ├── logo-512.png │ ├── logo-original.jpg │ ├── logo-original.png │ └── logo-original.svg ├── LICENSE ├── Prometheus.Client.groupproj ├── README.md ├── Samples ├── Starter-DMVC │ ├── Controllers.Demo.pas │ ├── Starter.Sample.DMVC.dpr │ ├── Starter.Sample.DMVC.dproj │ ├── Starter.Sample.DMVC.res │ ├── WebModules.App.dfm │ └── WebModules.App.pas ├── Starter-Horse │ ├── Starter.Sample.Horse.dpr │ ├── Starter.Sample.Horse.dproj │ ├── Starter.Sample.Horse.res │ ├── Starter.Sample.HorseApp.dpr │ ├── Starter.Sample.HorseApp.dproj │ └── Starter.Sample.HorseApp.res ├── Starter-WebBroker │ ├── Forms.Main.dfm │ ├── Forms.Main.pas │ ├── Services.Memory.pas │ ├── Starter.Sample.WebBroker.dpr │ ├── Starter.Sample.WebBroker.dproj │ ├── Starter.Sample.WebBroker.res │ ├── Starter.Sample.WebBrokerApp.dpr │ ├── Starter.Sample.WebBrokerApp.dproj │ ├── WebModules.Prom.dfm │ └── WebModules.Prom.pas └── Starter-WiRL │ ├── Server.Forms.Main.dfm │ ├── Server.Forms.Main.pas │ ├── Server.Resources.Metrics.pas │ ├── Server.Resources.Samples.pas │ ├── Starter.Sample.WiRL.dpr │ ├── Starter.Sample.WiRL.dproj │ ├── Starter.Sample.WiRL.res │ ├── Starter.Sample.WiRLApp.dpr │ └── Starter.Sample.WiRLApp.dproj ├── Source ├── Prometheus.Client.Core.dpk ├── Prometheus.Client.Core.dproj ├── Prometheus.Client.Core.res ├── Prometheus.Collector.pas ├── Prometheus.Collectors.Counter.pas ├── Prometheus.Collectors.Gauge.pas ├── Prometheus.Collectors.Histogram.pas ├── Prometheus.Exposers.Text.pas ├── Prometheus.Labels.pas ├── Prometheus.Metrics.pas ├── Prometheus.Registry.pas ├── Prometheus.Resources.pas ├── Prometheus.Samples.pas └── Prometheus.SimpleCollector.pas ├── Tests ├── Fixtures │ ├── Prometheus.Tests.Fixtures.Collector.pas │ ├── Prometheus.Tests.Fixtures.Collectors.Counter.pas │ ├── Prometheus.Tests.Fixtures.Collectors.Gauge.pas │ └── Prometheus.Tests.Fixtures.Collectors.Histogram.pas ├── Prometheus.Client.Tests.dpr ├── Prometheus.Client.Tests.dproj └── Prometheus.Client.Tests.res ├── boss-lock.json └── boss.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Uncomment these types if you want even more clean repository. But be careful. 2 | # It can make harm to an existing project source. Read explanations below. 3 | # 4 | # Resource files are binaries containing manifest, project icon and version info. 5 | # They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. 6 | #*.res 7 | # 8 | # Type library file (binary). In old Delphi versions it should be stored. 9 | # Since Delphi 2009 it is produced from .ridl file and can safely be ignored. 10 | #*.tlb 11 | # 12 | # Diagram Portfolio file. Used by the diagram editor up to Delphi 7. 13 | # Uncomment this if you are not using diagrams or use newer Delphi version. 14 | #*.ddp 15 | # 16 | # Visual LiveBindings file. Added in Delphi XE2. 17 | # Uncomment this if you are not using LiveBindings Designer. 18 | #*.vlb 19 | # 20 | # Deployment Manager configuration file for your project. Added in Delphi XE2. 21 | # Uncomment this if it is not mobile development and you do not use remote debug feature. 22 | #*.deployproj 23 | # 24 | # C++ object files produced when C/C++ Output file generation is configured. 25 | # Uncomment this if you are not using external objects (zlib library for example). 26 | #*.obj 27 | # 28 | 29 | # Delphi compiler-generated binaries (safe to delete) 30 | *.exe 31 | *.dll 32 | *.bpl 33 | *.bpi 34 | *.dcp 35 | *.so 36 | *.apk 37 | *.drc 38 | *.map 39 | *.dres 40 | *.rsm 41 | *.tds 42 | *.dcu 43 | *.lib 44 | *.a 45 | *.o 46 | *.ocx 47 | 48 | # Delphi autogenerated files (duplicated info) 49 | *.cfg 50 | *.hpp 51 | *Resource.rc 52 | 53 | # Delphi local files (user-specific info) 54 | *.local 55 | *.identcache 56 | *.projdata 57 | *.tvsconfig 58 | *.dsk 59 | *.delphilsp.json 60 | 61 | # Delphi history and backups 62 | __history/ 63 | __recovery/ 64 | *.~* 65 | 66 | # Castalia statistics file (since XE7 Castalia is distributed with Delphi) 67 | *.stat 68 | 69 | # DUnitX 70 | dunitx-results.xml 71 | 72 | # Boss dependency manager vendor folder https://github.com/HashLoad/boss 73 | modules/ 74 | 75 | -------------------------------------------------------------------------------- /Assets/Logo/logo-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcobreveglieri/prometheus-client-delphi/f3bb03adc4f9cf92ef7978f296943e40437a05b7/Assets/Logo/logo-1024.png -------------------------------------------------------------------------------- /Assets/Logo/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcobreveglieri/prometheus-client-delphi/f3bb03adc4f9cf92ef7978f296943e40437a05b7/Assets/Logo/logo-128.png -------------------------------------------------------------------------------- /Assets/Logo/logo-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcobreveglieri/prometheus-client-delphi/f3bb03adc4f9cf92ef7978f296943e40437a05b7/Assets/Logo/logo-512.png -------------------------------------------------------------------------------- /Assets/Logo/logo-original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcobreveglieri/prometheus-client-delphi/f3bb03adc4f9cf92ef7978f296943e40437a05b7/Assets/Logo/logo-original.jpg -------------------------------------------------------------------------------- /Assets/Logo/logo-original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcobreveglieri/prometheus-client-delphi/f3bb03adc4f9cf92ef7978f296943e40437a05b7/Assets/Logo/logo-original.png -------------------------------------------------------------------------------- /Assets/Logo/logo-original.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 227 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Marco Breveglieri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Prometheus.Client.groupproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {B47049D2-F3BA-413F-92FE-EF4066D88FCA} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Default.Personality.12 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Prometheus Client for Delphi 3 |

Prometheus Client for Delphi

4 |
5 |
6 | 7 | This is a Delphi client library for [Prometheus](http://prometheus.io), similar to [libraries created for other languages](https://prometheus.io/docs/instrumenting/writing_clientlibs/). 8 | 9 | ## Overview 10 | 11 | The **Prometheus Delphi Client** library is a set of classes that allow you to instrument your Delphi applications with *Prometheus* metrics. 12 | 13 | It allows you to instrument your Delphi code with custom metrics and provides some built-in and ready to use metrics. 14 | 15 | The library also supports Prometheus' text based exposition format, that can be configured and made available via an HTTP endpoint on your Web application's instance using specific middlewares or directly calling the text exporter. 16 | 17 | ### What is Prometheus 18 | 19 | [Prometheus](http://prometheus.io) is a popular open-source monitoring tool that is widely used in modern software environments. It provides a powerful system for collecting and analyzing metrics from various sources, including applications, servers, and other systems. 20 | 21 | To use *Prometheus* effectively, you need a [client library](https://prometheus.io/docs/instrumenting/clientlibs/) implemented in your favorite programming language that can be integrated into your applications to expose the relevant metrics to the Prometheus server. 22 | 23 | Here we will discuss the client library for Prometheus written for [Embarcadero Delphi](https://www.embarcadero.com/products/delphi/). 24 | 25 | ### Main Features 26 | 27 | The Prometheus Delphi Client library offers a **range of features** that make it a powerful and flexible tool for monitoring Delphi applications using Prometheus. 28 | 29 | By using the library, you can gain valuable insights into the performance and behavior of your Delphi applications and make data-driven decisions to improve them. 30 | 31 | Here are some of supported features: 32 | 33 | + **Basic metrics**: the library allows you to define some basic metrics supported by Prometheus to track some relevant values in your application, like the number of times an event has occured or the current amount of allocated memory and so on. 34 | + **Labels**: these are key-value pairs that allow you to add additional context to your metrics. 35 | + **Custom Collectors**: the library allows you to define custom collectors that can be used to collect metrics from any source. 36 | 37 | ## Getting Started 38 | 39 | To get started with the Prometheus Delphi Client library, you need to follow these steps. 40 | 41 | ### ⚙ Install the library 42 | 43 | Installation is done using the [`boss install`](https://github.com/HashLoad/boss) command: 44 | ``` sh 45 | boss install marcobreveglieri/prometheus-client-delphi 46 | ``` 47 | If you choose to install it manually, download the source code from GitHub simply add the following folders to your project, in *Project > Options > Resource Compiler > Directories and Conditionals > Include file search path* 48 | ``` 49 | prometheus-client-delphi/Source 50 | ``` 51 | 52 | ### 📏 Define your metrics 53 | 54 | Define the metrics you want to track using the appropriate classes (see below). 55 | 56 | ### 📒 Register your metrics 57 | 58 | Register your metrics inside the default collector registry or in a registry of your own for subsequent handling and exportation. 59 | 60 | ### ✔ Update your metrics 61 | 62 | Update your metrics as needed calling the appropriate methods you can find on collector instance depending on the classes they are based to. 63 | 64 | ### 💾 Export all your metric samples 65 | 66 | You can export your metrics calling the text based exporter or making use of a ready to use middleware that targets your favourite Delphi Web framework (see [Middlewares](#Middlewares) section below for details). 67 | 68 | ## Metrics 69 | 70 | Prometheus Delphi Client supports the following metric types. 71 | 72 | ### Counter 73 | 74 | A **counter** is a cumulative metric that represents a single monotonically increasing counter whose value can only increase or be reset to zero on restart. For example, you can use a counter to represent the number of requests served, tasks completed, or errors. 75 | 76 | Do not use a counter to expose a value that can decrease. For example, do not use a counter for the number of currently running processes; instead use a gauge. 77 | 78 | ```delphi 79 | uses 80 | Prometheus.Collectors.Counter; 81 | 82 | begin 83 | var LCounter := TCounter.Create('sample', 'Description of this counter'); 84 | LCounter.Inc(); // increment by 1 85 | LCounter.Inc(123); // increment by 123 86 | end. 87 | ``` 88 | 89 | ### Gauge 90 | 91 | A **gauge** is a metric that represents a single numerical value that can arbitrarily go up and down. 92 | 93 | Gauges are typically used for measured values like temperatures or current memory usage, but also "counts" that can go up and down, like the number of concurrent requests. 94 | 95 | ```delphi 96 | uses 97 | Prometheus.Collectors.Gauge; 98 | 99 | begin 100 | var LGauge := TGauge.Create('sample', 'Description of this gauge'); 101 | LGauge.Inc(); // increment by 1 102 | LGauge.Inc(123); // increment by 123 103 | LGauge.Dec(10); // decrement by 10 104 | LGauge.SetTo(123); // set value directly to 123 105 | LGauge.SetDuration( // set value to duration of method execution 106 | procedure 107 | begin 108 | // User code 109 | end); 110 | end. 111 | ``` 112 | 113 | ### Histogram 114 | 115 | A **histogram** samples observations (usually things like request durations or response sizes) and counts them in configurable buckets. It also provides a sum of all observed values. 116 | 117 | ```delphi 118 | uses 119 | Prometheus.Collectors.Histogram; 120 | 121 | begin 122 | LHistogram := THistogram.Create('Name of histogram metric', 'Help text for histogram metric'); 123 | // If buckets argument is not supplied, the default values will be used: 124 | // [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, INFINITE]. 125 | LHistogram.Observe(0.01); 126 | LHistogram.Observe(0.04); 127 | LHistogram.Observe(1); 128 | end. 129 | ``` 130 | 131 | ### Summary 132 | 133 | Similar to a histogram, a **summary** samples observations (usually things like request durations and response sizes). While it also provides a total count of observations and a sum of all observed values, it calculates configurable quantiles over a sliding time window. 134 | 135 | *** !!! Under Development !!! *** 136 | 137 | ### Custom metrics 138 | 139 | You can also implement your own custom metrics by inheriting the appropriate classes (**TCollector** or **TSimpleCollector**). 140 | 141 | ## Labels 142 | 143 | All metrics can have **labels**, allowing grouping of related time series. 144 | 145 | Taking a counter as an example: 146 | 147 | ```delphi 148 | uses 149 | Prometheus.Collectors.Counter; 150 | 151 | begin 152 | var LCounter := TCounter 153 | .Create('http_requests_handled', 'HTTP handled requests total', ['path', 'status']) 154 | .Register(); 155 | end. 156 | ``` 157 | 158 | Metrics with labels are not initialized when declared, because the client can't know what values the label can have. 159 | It is recommended to initialize the label values by calling the appropriate method and then eventually call another method to alter the value of the metric associated to label values: 160 | 161 | ```delphi 162 | uses 163 | Prometheus.Collectors.Counter; 164 | 165 | begin 166 | TCollectorRegistry.DefaultRegistry 167 | .GetCollector('http_requests_handled') 168 | .Labels(['/api', 200]) // ['path', 'status'] 169 | .Inc(); // increment child counter attached to these label values 170 | end. 171 | ``` 172 | 173 | ## Exporting metrics 174 | 175 | There are several options for exporting metrics. For example, you can export metrics from a *Windows Service Application* using a **TIdHttp** server component from *Indy Components* and exposing a "/metrics" endpoint where you export text based metrics data to Prometheus server. 176 | 177 | You can also download a middleware for your favourite Web framework or take a look at the sample projects. 178 | 179 | ## Middlewares 180 | 181 | To ease the use of Prometheus Client inside Web applications created with Delphi, you will find here **middlewares** to download and install. 182 | 183 | Each middleware integrates support for exposing metrics to Prometheus server using the appropriate format and without having to code each endpoint manually. 184 | 185 | You can find official **Prometheus Client middlewares** into these separate repositories: 186 | 187 | | Middleware | 188 | | ------------------------------------------------------------------------------------------ | 189 | | [Delphi MVC Framework](https://github.com/marcobreveglieri/dmvc-prometheus-metrics) | 190 | | [Horse](https://github.com/marcobreveglieri/horse-prometheus-metrics) | 191 | 192 | ## Delphi compatibility 193 | 194 | *Prometheus Client* works with **Delphi 11 Alexandria** as it makes use of advanced features of Delphi language, but with some slight changes it maybe could work in previous versions. 195 | 196 | ## Additional links 197 | 198 | + [Prometheus Official Page](https://prometheus.io) 199 | + [Using Delphi with Prometheus and Grafana (in Italian language)](https://www.youtube.com/watch?v=-bPDl6MP6jo) 200 | 201 | -------------------------------------------------------------------------------- /Samples/Starter-DMVC/Controllers.Demo.pas: -------------------------------------------------------------------------------- 1 | unit Controllers.Demo; 2 | 3 | interface 4 | 5 | uses 6 | System.Diagnostics, 7 | MVCFramework, 8 | MVCFramework.Commons, 9 | Prometheus.Collectors.Histogram; 10 | 11 | type 12 | 13 | { TDemoController } 14 | 15 | [MVCPath('/')] 16 | TDemoController = class(TMVCController) 17 | private 18 | FDuration: TStopwatch; 19 | function ResponseDurationHistogram: THistogram; 20 | function ResponseLengthHistogram: THistogram; 21 | function FilesSentHistogram: THistogram; 22 | protected 23 | procedure OnBeforeAction(AContext: TWebContext; const AActionName: string; 24 | var AHandled: Boolean); override; 25 | procedure OnAfterAction(AContext: TWebContext; const AActionName: string); override; 26 | public 27 | [MVCPath('/')] 28 | [MVCHTTPMethod([httpGET])] 29 | [MVCProduces(TMVCMediaType.TEXT_PLAIN)] 30 | procedure HelloWorld; 31 | [MVCPath('/redirect')] 32 | [MVCHTTPMethod([httpGET])] 33 | [MVCProduces(TMVCMediaType.TEXT_PLAIN)] 34 | procedure RedirectTo; 35 | end; 36 | 37 | implementation 38 | 39 | uses 40 | System.SysUtils, 41 | Prometheus.Collectors.Counter, 42 | Prometheus.Registry; 43 | 44 | { TDemoController } 45 | 46 | procedure TDemoController.HelloWorld; 47 | begin 48 | // Get the metric counter and increment it. 49 | TCollectorRegistry.DefaultRegistry 50 | .GetCollector('http_requests_count') 51 | .Inc(); 52 | Sleep(Random(1000)); 53 | // Render a sample string of text. 54 | Render('Hello World! It''s ' + TimeToStr(Time) + ' in the DMVCFramework Land!'); 55 | end; 56 | 57 | procedure TDemoController.RedirectTo; 58 | begin 59 | Redirect('/'); 60 | end; 61 | 62 | function TDemoController.FilesSentHistogram: THistogram; 63 | begin 64 | Result := TCollectorRegistry.DefaultRegistry 65 | .GetCollector('files_sent'); 66 | end; 67 | 68 | function TDemoController.ResponseDurationHistogram: THistogram; 69 | begin 70 | Result := TCollectorRegistry.DefaultRegistry.GetCollector('request_duration_seconds'); 71 | end; 72 | 73 | function TDemoController.ResponseLengthHistogram: THistogram; 74 | begin 75 | Result := TCollectorRegistry.DefaultRegistry.GetCollector('response_length'); 76 | end; 77 | 78 | procedure TDemoController.OnAfterAction(AContext: TWebContext; const AActionName: string); 79 | begin 80 | inherited; 81 | ResponseDurationHistogram 82 | .Labels([AContext.Request.PathInfo, AContext.Response.StatusCode.ToString ]) 83 | .Observe(FDuration.Elapsed.TotalSeconds); 84 | ResponseLengthHistogram 85 | .Observe(AContext.Response.RawWebResponse.ContentLength); 86 | FilesSentHistogram.Observe(Random(50)); 87 | end; 88 | 89 | procedure TDemoController.OnBeforeAction(AContext: TWebContext; const AActionName: string; var AHandled: Boolean); 90 | begin 91 | FDuration := TStopwatch.Create; 92 | FDuration.Start; 93 | inherited; 94 | end; 95 | 96 | end. 97 | -------------------------------------------------------------------------------- /Samples/Starter-DMVC/Starter.Sample.DMVC.dpr: -------------------------------------------------------------------------------- 1 | program Starter.Sample.DMVC; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | 6 | uses 7 | System.SysUtils, 8 | Web.WebReq, 9 | IdHTTPWebBrokerBridge, 10 | WebModules.App in 'WebModules.App.pas' {AppWebModule: TWebModule}, 11 | Controllers.Demo in 'Controllers.Demo.pas'; 12 | 13 | {$R *.res} 14 | 15 | var 16 | LServer: TIdHTTPWebBrokerBridge; 17 | 18 | procedure StartServer(APort: Integer); 19 | begin 20 | Writeln(Format('Starting HTTP Server or port %d', [APort])); 21 | LServer := TIdHTTPWebBrokerBridge.Create(nil); 22 | try 23 | LServer.DefaultPort := APort; 24 | LServer.MaxConnections := 0; 25 | LServer.ListenQueue := 200; 26 | LServer.Active := True; 27 | WriteLn('Press ENTER to quit the server.'); 28 | Readln; 29 | finally 30 | LServer.Free; 31 | end; 32 | end; 33 | 34 | begin 35 | ReportMemoryLeaksOnShutdown := True; 36 | try 37 | if WebRequestHandler <> nil then 38 | WebRequestHandler.WebModuleClass := WebModuleClass; 39 | WebRequestHandlerProc.MaxConnections := 1024; 40 | StartServer(9000); 41 | except 42 | on E: Exception do 43 | Writeln(E.ClassName, ': ', E.Message); 44 | end; 45 | end. 46 | -------------------------------------------------------------------------------- /Samples/Starter-DMVC/Starter.Sample.DMVC.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcobreveglieri/prometheus-client-delphi/f3bb03adc4f9cf92ef7978f296943e40437a05b7/Samples/Starter-DMVC/Starter.Sample.DMVC.res -------------------------------------------------------------------------------- /Samples/Starter-DMVC/WebModules.App.dfm: -------------------------------------------------------------------------------- 1 | object AppWebModule: TAppWebModule 2 | OnCreate = WebModuleCreate 3 | OnDestroy = WebModuleDestroy 4 | Actions = < 5 | item 6 | Default = True 7 | Name = 'DefaultHandler' 8 | PathInfo = '/' 9 | end> 10 | Height = 288 11 | Width = 519 12 | PixelsPerInch = 120 13 | end 14 | -------------------------------------------------------------------------------- /Samples/Starter-DMVC/WebModules.App.pas: -------------------------------------------------------------------------------- 1 | unit WebModules.App; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | System.SysUtils, 8 | Web.HTTPApp, 9 | MVCFramework; 10 | 11 | type 12 | 13 | { TAppWebModule } 14 | 15 | TAppWebModule = class(TWebModule) 16 | procedure WebModuleCreate(Sender: TObject); 17 | procedure WebModuleDestroy(Sender: TObject); 18 | private 19 | FEngine: TMVCEngine; 20 | end; 21 | 22 | var 23 | WebModuleClass: TComponentClass = TAppWebModule; 24 | 25 | implementation 26 | 27 | {$R *.dfm} 28 | 29 | uses 30 | MVCFramework.Commons, 31 | Prometheus.Collectors.Counter, 32 | Prometheus.Collectors.Histogram, 33 | Prometheus.Registry, 34 | Controllers.Demo; 35 | 36 | { TAppWebModule } 37 | 38 | procedure TAppWebModule.WebModuleCreate(Sender: TObject); 39 | begin 40 | // Creates the Delphi MVC Framework server application engine. 41 | FEngine := TMVCEngine.Create(Self); 42 | 43 | // Add a sample controller. 44 | FEngine.AddController(TDemoController); 45 | 46 | // Configure some sample metrics... 47 | 48 | // ... a simple counter 49 | TCounter.Create('http_requests_count', 'Received HTTP request count').Register(); 50 | 51 | // ... A request time histogram with two labels for path and status 52 | THistogram.Create( 53 | 'request_duration_seconds', 'Time taken to process request- in seconds', 54 | [0.05, 0.1, 0.25, 0.5, 1, 2, 10], ['path', 'status']) 55 | .Register(); 56 | 57 | // .. A request time histogram with no labels and an increasing bucket sequence 58 | THistogram.Create('response_length', 'Number of bytes sent in response', 10, 3, 10, []).Register(); 59 | 60 | // .. A request time histogram with no labels and a custom linear increasing bucket sequence 61 | THistogram.Create('files_sent', 'Number of files sent in response', 62 | function : TBuckets 63 | var 64 | LNextValue: Double; 65 | const 66 | StartValue = 10; 67 | ValueCount = 5; 68 | StepValue = 5; 69 | begin 70 | SetLength(Result, ValueCount); 71 | LNextValue := StartValue; 72 | for var LIndex := 0 to ValueCount - 1 do 73 | begin 74 | Result[LIndex] := LNextValue; 75 | LNextValue := LNextValue + StepValue; 76 | end; 77 | end, 78 | []) 79 | .Register(); 80 | 81 | FEngine.SetExceptionHandler( 82 | procedure(E: Exception; SelectedController: TMVCController; 83 | WebContext: TWebContext; var ExceptionHandled: Boolean) 84 | const 85 | AssumedDuration = 0.05; // seconds 86 | begin 87 | // needs a duration, will hard code it 88 | TCollectorRegistry.DefaultRegistry 89 | .GetCollector('request_duration_seconds') 90 | .Labels([WebContext.Request.PathInfo, WebContext.Response.StatusCode.ToString]) 91 | .Observe(AssumedDuration); 92 | end); 93 | end; 94 | 95 | procedure TAppWebModule.WebModuleDestroy(Sender: TObject); 96 | begin 97 | FEngine.Free; 98 | end; 99 | 100 | end. 101 | -------------------------------------------------------------------------------- /Samples/Starter-Horse/Starter.Sample.Horse.dpr: -------------------------------------------------------------------------------- 1 | program Starter.Sample.Horse; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | {$R *.res} 6 | 7 | uses 8 | System.Classes, 9 | System.SysUtils, 10 | Horse, 11 | Prometheus.Collectors.Counter, 12 | Prometheus.Exposers.Text, 13 | Prometheus.Registry; 14 | 15 | begin 16 | 17 | try 18 | 19 | (* 20 | PROMETHEUS SETUP 21 | *) 22 | 23 | // Creates a Prometheus "counter" metric to count HTTP handled requests 24 | // and registers it into the default collector registry for later access; 25 | // the counter will store different values varying by path and status code. 26 | TCounter 27 | .Create('http_requests_handled', 'Count all HTTP handled requests', ['path', 'status']) 28 | .Register(); 29 | 30 | // 31 | // NOTE!! If you don't want to implement the endpoint below for any of your 32 | // web project, consider downloading and installing this Horse middleware: 33 | // https://github.com/marcobreveglieri/horse-prometheus-metrics 34 | // 35 | 36 | // Creates and endpoint for Horse web framework to expose metric values. 37 | THorse.Get('/metrics', 38 | procedure(Req: THorseRequest; Res: THorseResponse) 39 | begin 40 | 41 | // We create a stream that will contain metric values exposed as text, 42 | // using the appropriate exposer from Prometheus Client to render it. 43 | var LStream := TMemoryStream.Create; 44 | try 45 | var LExposer := TTextExposer.Create; 46 | try 47 | LExposer.Render(LStream, TCollectorRegistry.DefaultRegistry.Collect()); 48 | finally 49 | LExposer.Free; 50 | end; 51 | except 52 | LStream.Free; 53 | raise; 54 | end; 55 | 56 | // Let's send all the text to the client. 57 | Res.RawWebResponse.ContentStream := LStream; 58 | Res.RawWebResponse.ContentType := Format('text/plain; charset=%s', ['utf-8']); 59 | Res.RawWebResponse.StatusCode := Integer(THTTPStatus.OK); 60 | Res.RawWebResponse.SendResponse; 61 | end); 62 | 63 | (* 64 | HORSE APPLICATION 65 | *) 66 | 67 | // Creates a test endpoint using Horse web framework. 68 | THorse.Get('/ping', 69 | procedure(Req: THorseRequest; Res: THorseResponse) 70 | begin 71 | // Increments the "counter" metric value specifying label values. 72 | TCollectorRegistry.DefaultRegistry 73 | .GetCollector('http_requests_handled') 74 | .Labels([Req.PathInfo, IntToStr(Res.Status)]) // ['path', 'status'] 75 | .Inc(); 76 | 77 | // Sends a sample response to the client. 78 | Res.Send('pong'); 79 | end); 80 | 81 | // Creates another test endpoint using Horse web framework. 82 | THorse.Get('/secret', 83 | procedure(Req: THorseRequest; Res: THorseResponse) 84 | begin 85 | // You are not authorized to see this! 86 | Res.Status(THTTPStatus.Unauthorized); 87 | 88 | // Increments the "counter" metric value specifying label values. 89 | TCollectorRegistry.DefaultRegistry 90 | .GetCollector('http_requests_handled') 91 | .Labels([Req.PathInfo, IntToStr(Res.Status)]) // ['path', 'status'] 92 | .Inc(); 93 | 94 | // Sends a sample response to the client. 95 | Res.Send('Access denied'); 96 | end); 97 | 98 | // Starts the Horse web server listening to port 9000. 99 | THorse.Listen(9000); 100 | 101 | except 102 | on E: Exception do 103 | Writeln(E.ClassName, ': ', E.Message); 104 | end; 105 | 106 | end. 107 | -------------------------------------------------------------------------------- /Samples/Starter-Horse/Starter.Sample.Horse.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcobreveglieri/prometheus-client-delphi/f3bb03adc4f9cf92ef7978f296943e40437a05b7/Samples/Starter-Horse/Starter.Sample.Horse.res -------------------------------------------------------------------------------- /Samples/Starter-Horse/Starter.Sample.HorseApp.dpr: -------------------------------------------------------------------------------- 1 | program Starter.Sample.HorseApp; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | {$R *.res} 6 | 7 | uses 8 | System.Classes, 9 | System.SysUtils, 10 | Horse, 11 | Prometheus.Collectors.Counter, 12 | Prometheus.Exposers.Text, 13 | Prometheus.Registry; 14 | 15 | begin 16 | 17 | try 18 | 19 | (* 20 | PROMETHEUS SETUP 21 | *) 22 | 23 | // Creates a Prometheus "counter" metric to count HTTP handled requests 24 | // and registers it into the default collector registry for later access; 25 | // the counter will store different values varying by path and status code. 26 | TCounter 27 | .Create('http_requests_handled', 'Count all HTTP handled requests', ['path', 'status']) 28 | .Register(); 29 | 30 | // 31 | // NOTE!! If you don't want to implement the endpoint below for any of your 32 | // web project, consider downloading and installing this Horse middleware: 33 | // https://github.com/marcobreveglieri/horse-prometheus-metrics 34 | // 35 | 36 | // Creates and endpoint for Horse web framework to expose metric values. 37 | THorse.Get('/metrics', 38 | procedure(Req: THorseRequest; Res: THorseResponse) 39 | begin 40 | 41 | // We create a stream that will contain metric values exposed as text, 42 | // using the appropriate exposer from Prometheus Client to render it. 43 | var LStream := TMemoryStream.Create; 44 | try 45 | var LExposer := TTextExposer.Create; 46 | try 47 | LExposer.Render(LStream, TCollectorRegistry.DefaultRegistry.Collect()); 48 | finally 49 | LExposer.Free; 50 | end; 51 | except 52 | LStream.Free; 53 | raise; 54 | end; 55 | 56 | // Let's send all the text to the client. 57 | Res.RawWebResponse.ContentStream := LStream; 58 | Res.RawWebResponse.ContentType := Format('text/plain; charset=%s', ['utf-8']); 59 | Res.RawWebResponse.StatusCode := Integer(THTTPStatus.OK); 60 | Res.RawWebResponse.SendResponse; 61 | end); 62 | 63 | (* 64 | HORSE APPLICATION 65 | *) 66 | 67 | // Creates a test endpoint using Horse web framework. 68 | THorse.Get('/ping', 69 | procedure(Req: THorseRequest; Res: THorseResponse) 70 | begin 71 | // Increments the "counter" metric value specifying label values. 72 | TCollectorRegistry.DefaultRegistry 73 | .GetCollector('http_requests_handled') 74 | .Labels([Req.PathInfo, IntToStr(Res.Status)]) // ['path', 'status'] 75 | .Inc(); 76 | 77 | // Sends a sample response to the client. 78 | Res.Send('pong'); 79 | end); 80 | 81 | // Creates another test endpoint using Horse web framework. 82 | THorse.Get('/secret', 83 | procedure(Req: THorseRequest; Res: THorseResponse) 84 | begin 85 | // You are not authorized to see this! 86 | Res.Status(THTTPStatus.Unauthorized); 87 | 88 | // Increments the "counter" metric value specifying label values. 89 | TCollectorRegistry.DefaultRegistry 90 | .GetCollector('http_requests_handled') 91 | .Labels([Req.PathInfo, IntToStr(Res.Status)]) // ['path', 'status'] 92 | .Inc(); 93 | 94 | // Sends a sample response to the client. 95 | Res.Send('Access denied'); 96 | end); 97 | 98 | // Starts the Horse web server listening to port 9000. 99 | THorse.Listen(9000); 100 | 101 | except 102 | on E: Exception do 103 | Writeln(E.ClassName, ': ', E.Message); 104 | end; 105 | 106 | end. 107 | -------------------------------------------------------------------------------- /Samples/Starter-Horse/Starter.Sample.HorseApp.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcobreveglieri/prometheus-client-delphi/f3bb03adc4f9cf92ef7978f296943e40437a05b7/Samples/Starter-Horse/Starter.Sample.HorseApp.res -------------------------------------------------------------------------------- /Samples/Starter-WebBroker/Forms.Main.dfm: -------------------------------------------------------------------------------- 1 | object MainForm: TMainForm 2 | Left = 271 3 | Top = 114 4 | BorderIcons = [biSystemMenu, biMinimize] 5 | BorderStyle = bsSingle 6 | Caption = 'Prometheus WebBroker Demo' 7 | ClientHeight = 227 8 | ClientWidth = 333 9 | Color = clBtnFace 10 | Font.Charset = ANSI_CHARSET 11 | Font.Color = clWindowText 12 | Font.Height = -13 13 | Font.Name = 'Segoe UI' 14 | Font.Style = [] 15 | ShowHint = True 16 | TextHeight = 17 17 | object PortLabel: TLabel 18 | Left = 192 19 | Top = 17 20 | Width = 24 21 | Height = 17 22 | Caption = 'Port' 23 | end 24 | object StartServerButton: TButton 25 | Left = 8 26 | Top = 16 27 | Width = 156 28 | Height = 49 29 | Action = StartServerAction 30 | Default = True 31 | TabOrder = 0 32 | end 33 | object StopServerButton: TButton 34 | Left = 8 35 | Top = 72 36 | Width = 156 37 | Height = 49 38 | Action = StopServerAction 39 | Cancel = True 40 | Default = True 41 | TabOrder = 1 42 | end 43 | object PortEdit: TEdit 44 | Left = 192 45 | Top = 40 46 | Width = 121 47 | Height = 25 48 | TabOrder = 2 49 | Text = '8081' 50 | end 51 | object OpenBrowserButton: TButton 52 | Left = 168 53 | Top = 168 54 | Width = 156 55 | Height = 49 56 | Action = OpenBrowserAction 57 | TabOrder = 3 58 | end 59 | object MainActionList: TActionList 60 | OnUpdate = MainActionListUpdate 61 | Left = 40 62 | Top = 168 63 | object StartServerAction: TAction 64 | Caption = '&Start Server' 65 | OnExecute = StartServerActionExecute 66 | end 67 | object StopServerAction: TAction 68 | Caption = 'Sto&p Server' 69 | OnExecute = StopServerActionExecute 70 | end 71 | object OpenBrowserAction: TAction 72 | Caption = '&Open Browser' 73 | OnExecute = OpenBrowserActionExecute 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /Samples/Starter-WebBroker/Forms.Main.pas: -------------------------------------------------------------------------------- 1 | unit Forms.Main; 2 | 3 | interface 4 | 5 | uses 6 | Winapi.Messages, System.SysUtils, System.Variants, 7 | System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, 8 | Vcl.AppEvnts, Vcl.StdCtrls, IdHTTPWebBrokerBridge, IdGlobal, Web.HTTPApp, 9 | System.Actions, Vcl.ActnList; 10 | 11 | type 12 | 13 | TMainForm = class(TForm) 14 | StartServerButton: TButton; 15 | StopServerButton: TButton; 16 | PortEdit: TEdit; 17 | PortLabel: TLabel; 18 | OpenBrowserButton: TButton; 19 | MainActionList: TActionList; 20 | StartServerAction: TAction; 21 | StopServerAction: TAction; 22 | OpenBrowserAction: TAction; 23 | procedure MainActionListUpdate(Action: TBasicAction; var Handled: Boolean); 24 | procedure StartServerActionExecute(Sender: TObject); 25 | procedure StopServerActionExecute(Sender: TObject); 26 | procedure OpenBrowserActionExecute(Sender: TObject); 27 | private 28 | FServer: TIdHTTPWebBrokerBridge; 29 | public 30 | constructor Create(AOwner: TComponent); override; 31 | destructor Destroy; override; 32 | end; 33 | 34 | var 35 | MainForm: TMainForm; 36 | 37 | implementation 38 | 39 | {$R *.dfm} 40 | 41 | uses 42 | Winapi.ShellApi, 43 | WinApi.Windows; 44 | 45 | constructor TMainForm.Create(AOwner: TComponent); 46 | begin 47 | inherited Create(AOwner); 48 | FServer := TIdHTTPWebBrokerBridge.Create(Self); 49 | end; 50 | 51 | destructor TMainForm.Destroy; 52 | begin 53 | if Assigned(FServer) then 54 | FreeAndNil(FServer); 55 | inherited Destroy; 56 | end; 57 | 58 | procedure TMainForm.MainActionListUpdate(Action: TBasicAction; var Handled: Boolean); 59 | var 60 | LServerRunning: Boolean; 61 | begin 62 | LServerRunning := FServer.Active; 63 | StartServerAction.Enabled := not LServerRunning; 64 | StopServerAction.Enabled := LServerRunning; 65 | OpenBrowserAction.Enabled := LServerRunning; 66 | PortEdit.Enabled := not LServerRunning; 67 | end; 68 | 69 | procedure TMainForm.OpenBrowserActionExecute(Sender: TObject); 70 | var 71 | LURL: string; 72 | begin 73 | if not FServer.Active then 74 | Exit; 75 | LURL := Format('http://localhost:%s', [PortEdit.Text]); 76 | ShellExecute(0, nil, PChar(LURL), nil, nil, SW_SHOWNOACTIVATE); 77 | end; 78 | 79 | procedure TMainForm.StartServerActionExecute(Sender: TObject); 80 | begin 81 | if FServer.Active then 82 | Exit; 83 | FServer.Bindings.Clear; 84 | FServer.DefaultPort := StrToInt(PortEdit.Text); 85 | FServer.Active := True; 86 | end; 87 | 88 | procedure TMainForm.StopServerActionExecute(Sender: TObject); 89 | begin 90 | if not FServer.Active then 91 | Exit; 92 | FServer.Active := False; 93 | FServer.Bindings.Clear; 94 | end; 95 | 96 | end. 97 | -------------------------------------------------------------------------------- /Samples/Starter-WebBroker/Services.Memory.pas: -------------------------------------------------------------------------------- 1 | unit Services.Memory; 2 | 3 | interface 4 | 5 | type 6 | 7 | { TMemoryServices } 8 | 9 | TMemoryServices = class 10 | public 11 | class function GetTotalAllocatedMemory: NativeUInt; 12 | end; 13 | 14 | implementation 15 | 16 | { TMemoryServices } 17 | 18 | class function TMemoryServices.GetTotalAllocatedMemory: NativeUInt; 19 | var 20 | LMMS: TMemoryManagerState; 21 | LSBTS: TSmallBlockTypeState; 22 | begin 23 | Result := 0; 24 | {$WARN SYMBOL_PLATFORM OFF} 25 | GetMemoryManagerState(LMMS); 26 | {$WARN SYMBOL_PLATFORM ON} 27 | for LSBTS in LMMS.SmallBlockTypeStates do 28 | Inc(Result, LSBTS.InternalBlockSize * LSBTS.AllocatedBlockCount); 29 | Inc(Result, LMMS.TotalAllocatedMediumBlockSize); 30 | Inc(Result, LMMS.TotalAllocatedLargeBlockSize); 31 | end; 32 | 33 | end. 34 | -------------------------------------------------------------------------------- /Samples/Starter-WebBroker/Starter.Sample.WebBroker.dpr: -------------------------------------------------------------------------------- 1 | program Starter.Sample.WebBroker; 2 | {$APPTYPE GUI} 3 | 4 | uses 5 | Vcl.Forms, 6 | Web.WebReq, 7 | IdHTTPWebBrokerBridge, 8 | Forms.Main in 'Forms.Main.pas' {MainForm}, 9 | WebModules.Prom in 'WebModules.Prom.pas' {PromWebModule: TWebModule}, 10 | Services.Memory in 'Services.Memory.pas'; 11 | 12 | {$R *.res} 13 | 14 | begin 15 | ReportMemoryLeaksOnShutdown := True; 16 | if WebRequestHandler <> nil then 17 | WebRequestHandler.WebModuleClass := WebModuleClass; 18 | Application.Initialize; 19 | Application.CreateForm(TMainForm, MainForm); 20 | Application.Run; 21 | end. 22 | -------------------------------------------------------------------------------- /Samples/Starter-WebBroker/Starter.Sample.WebBroker.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcobreveglieri/prometheus-client-delphi/f3bb03adc4f9cf92ef7978f296943e40437a05b7/Samples/Starter-WebBroker/Starter.Sample.WebBroker.res -------------------------------------------------------------------------------- /Samples/Starter-WebBroker/Starter.Sample.WebBrokerApp.dpr: -------------------------------------------------------------------------------- 1 | program Starter.Sample.WebBrokerApp; 2 | {$APPTYPE GUI} 3 | 4 | uses 5 | Vcl.Forms, 6 | Web.WebReq, 7 | IdHTTPWebBrokerBridge, 8 | Forms.Main in 'Forms.Main.pas' {MainForm}, 9 | WebModules.Prom in 'WebModules.Prom.pas' {PromWebModule: TWebModule}, 10 | Services.Memory in 'Services.Memory.pas'; 11 | 12 | {$R *.res} 13 | 14 | begin 15 | ReportMemoryLeaksOnShutdown := True; 16 | if WebRequestHandler <> nil then 17 | WebRequestHandler.WebModuleClass := WebModuleClass; 18 | Application.Initialize; 19 | Application.CreateForm(TMainForm, MainForm); 20 | Application.Run; 21 | end. 22 | -------------------------------------------------------------------------------- /Samples/Starter-WebBroker/WebModules.Prom.dfm: -------------------------------------------------------------------------------- 1 | object PromWebModule: TPromWebModule 2 | Actions = < 3 | item 4 | Default = True 5 | Enabled = False 6 | Name = 'DefaultHandler' 7 | PathInfo = '/' 8 | OnAction = PromWebModuleDefaultHandlerAction 9 | end 10 | item 11 | MethodType = mtGet 12 | Name = 'MetricAction' 13 | PathInfo = '/metrics' 14 | OnAction = PromWebModuleMetricActionAction 15 | end 16 | item 17 | MethodType = mtGet 18 | Name = 'Leak' 19 | PathInfo = '/leak' 20 | OnAction = PromWebModuleLeakAction 21 | end> 22 | BeforeDispatch = WebModuleBeforeDispatch 23 | AfterDispatch = WebModuleAfterDispatch 24 | Height = 312 25 | Width = 401 26 | end 27 | -------------------------------------------------------------------------------- /Samples/Starter-WebBroker/WebModules.Prom.pas: -------------------------------------------------------------------------------- 1 | unit WebModules.Prom; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | System.SysUtils, 8 | Web.HTTPApp; 9 | 10 | type 11 | TPromWebModule = class(TWebModule) 12 | procedure PromWebModuleDefaultHandlerAction(Sender: TObject; 13 | Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); 14 | procedure PromWebModuleMetricActionAction(Sender: TObject; 15 | Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); 16 | procedure PromWebModuleLeakAction(Sender: TObject; Request: TWebRequest; 17 | Response: TWebResponse; var Handled: Boolean); 18 | procedure WebModuleAfterDispatch(Sender: TObject; Request: TWebRequest; 19 | Response: TWebResponse; var Handled: Boolean); 20 | procedure WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest; 21 | Response: TWebResponse; var Handled: Boolean); 22 | private 23 | procedure InitializeMetrics; 24 | procedure UpdateLastMinuteMetrics; 25 | public 26 | constructor Create(AOwner: TComponent); override; 27 | end; 28 | 29 | var 30 | WebModuleClass: TComponentClass = TPromWebModule; 31 | 32 | implementation 33 | 34 | {%CLASSGROUP 'Vcl.Controls.TControl'} 35 | 36 | {$R *.dfm} 37 | 38 | uses 39 | Prometheus.Collectors.Counter, 40 | Prometheus.Collectors.Gauge, 41 | Prometheus.Registry, 42 | Prometheus.Exposers.Text, 43 | Services.Memory; 44 | 45 | constructor TPromWebModule.Create(AOwner: TComponent); 46 | begin 47 | inherited Create(AOwner); 48 | InitializeMetrics; 49 | end; 50 | 51 | procedure TPromWebModule.InitializeMetrics; 52 | begin 53 | var LRegistry := TCollectorRegistry.DefaultRegistry; 54 | // Counter: http_requests_count 55 | if not LRegistry.HasCollector('http_requests_count') then 56 | begin 57 | TCounter 58 | .Create('http_requests_count', 'HTTP received request count') 59 | .Register(); 60 | end; 61 | // Counter: http_requests_handled 62 | if not LRegistry.HasCollector('http_requests_handled') then 63 | begin 64 | TCounter 65 | .Create('http_requests_handled', 'HTTP handled request count', 66 | ['path', 'status']) 67 | .Register(); 68 | end; 69 | // Gauge: memory_allocated_total 70 | if not LRegistry.HasCollector('memory_allocated_total') then 71 | begin 72 | var LGauge := TGauge 73 | .Create('memory_allocated_total', 'Total memory allocated by the process'); 74 | LGauge.SetTo(TMemoryServices.GetTotalAllocatedMemory); 75 | LGauge.Register(); 76 | end; 77 | end; 78 | 79 | procedure TPromWebModule.PromWebModuleDefaultHandlerAction(Sender: TObject; 80 | Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); 81 | begin 82 | if Request.PathInfo <> '/' then 83 | begin 84 | Response.StatusCode := 404; 85 | Response.Content := 'Not found!'; 86 | Exit; 87 | end; 88 | Response.Content := 89 | '' + 90 | 'Web Server Application' + 91 | '' + 92 | '

Web Server Application

' + 93 | '

Prometheus Delphi Client powered!

' + 94 | '

View exposed Metrics

' + 95 | '' + 96 | ''; 97 | end; 98 | 99 | procedure TPromWebModule.PromWebModuleMetricActionAction(Sender: TObject; 100 | Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); 101 | begin 102 | UpdateLastMinuteMetrics; 103 | Response.ContentType := 'text/plain'; 104 | Response.ContentStream := TMemoryStream.Create; 105 | var LWriter := TTextExposer.Create; 106 | try 107 | LWriter.Render(Response.ContentStream, 108 | TCollectorRegistry.DefaultRegistry.Collect); 109 | finally 110 | LWriter.Free; 111 | end; 112 | end; 113 | 114 | procedure TPromWebModule.PromWebModuleLeakAction(Sender: TObject; 115 | Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); 116 | begin 117 | GetMemory(4096 * 10); // Intentional memory leak! 118 | Response.ContentType := 'text/plain'; 119 | Response.Content := 'Done.'; 120 | end; 121 | 122 | procedure TPromWebModule.UpdateLastMinuteMetrics; 123 | begin 124 | TCollectorRegistry.DefaultRegistry 125 | .GetCollector('memory_allocated_total') 126 | .SetTo(TMemoryServices.GetTotalAllocatedMemory); 127 | end; 128 | 129 | procedure TPromWebModule.WebModuleAfterDispatch(Sender: TObject; 130 | Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); 131 | begin 132 | var LPath := Request.PathInfo; 133 | var LStatus := IntToStr(Response.StatusCode); 134 | TCollectorRegistry.DefaultRegistry 135 | .GetCollector('http_requests_handled') 136 | .Labels([LPath, LStatus]) // ['path', 'status'] 137 | .Inc(); 138 | end; 139 | 140 | procedure TPromWebModule.WebModuleBeforeDispatch(Sender: TObject; 141 | Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); 142 | begin 143 | TCollectorRegistry.DefaultRegistry 144 | .GetCollector('http_requests_count') 145 | .Inc(); 146 | end; 147 | 148 | end. 149 | -------------------------------------------------------------------------------- /Samples/Starter-WiRL/Server.Forms.Main.pas: -------------------------------------------------------------------------------- 1 | unit Server.Forms.Main; 2 | 3 | interface 4 | 5 | uses 6 | System.Actions, 7 | System.Classes, 8 | System.SysUtils, 9 | Vcl.Forms, 10 | Vcl.ActnList, 11 | Vcl.ComCtrls, 12 | Vcl.StdCtrls, 13 | Vcl.Controls, 14 | Vcl.ExtCtrls, 15 | Vcl.Imaging.pngimage, 16 | WiRL.http.Server; 17 | 18 | type 19 | 20 | { TMainForm } 21 | 22 | TMainForm = class(TForm) 23 | TopPanel: TPanel; 24 | StartServerButton: TButton; 25 | StopServerButton: TButton; 26 | MainActionList: TActionList; 27 | StartServerAction: TAction; 28 | StopServerAction: TAction; 29 | PortNumberEdit: TEdit; 30 | PortNumberLabel: TLabel; 31 | WiRLImage: TImage; 32 | PromImage: TImage; 33 | procedure StartServerActionExecute(Sender: TObject); 34 | procedure StopServerActionExecute(Sender: TObject); 35 | procedure FormCreate(Sender: TObject); 36 | procedure FormClose(Sender: TObject; var Action: TCloseAction); 37 | procedure MainActionListUpdate(Action: TBasicAction; var Handled: Boolean); 38 | private 39 | FServer: TWiRLServer; 40 | end; 41 | 42 | var 43 | MainForm: TMainForm; 44 | 45 | implementation 46 | 47 | {$R *.dfm} 48 | 49 | uses 50 | WiRL.Core.Engine, 51 | WiRL.Core.Application, 52 | WiRL.http.Server.Indy, 53 | Prometheus.Registry, 54 | Prometheus.Collectors.Counter; 55 | 56 | { TMainForm } 57 | 58 | procedure TMainForm.FormCreate(Sender: TObject); 59 | begin 60 | StartServerAction.Execute; 61 | end; 62 | 63 | procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction); 64 | begin 65 | StopServerAction.Execute; 66 | end; 67 | 68 | procedure TMainForm.MainActionListUpdate(Action: TBasicAction; 69 | var Handled: Boolean); 70 | begin 71 | StartServerAction.Enabled := (FServer = nil) or (FServer.Active = False); 72 | StopServerAction.Enabled := not StartServerAction.Enabled; 73 | end; 74 | 75 | procedure TMainForm.StartServerActionExecute(Sender: TObject); 76 | begin 77 | // Create the WiRL HTTP Web Server. 78 | FServer := TWiRLServer.Create(nil); 79 | 80 | // Set up the server configuration. 81 | FServer 82 | .SetPort(StrToIntDef(PortNumberEdit.Text, 8080)) 83 | .AddEngine('/rest') 84 | .SetEngineName('WiRL ContentType Demo') 85 | .AddApplication('/app') 86 | .SetAppName('Content App') 87 | .SetWriters('*') 88 | .SetReaders('*') 89 | .SetResources('Server.Resources.*'); 90 | 91 | // Create a sample counter metric and register it into the default registry. 92 | TCounter 93 | .Create('http_requests_count', 'Received HTTP request count', ['path', 'status']) 94 | .Register(); 95 | 96 | // Start the Web server. 97 | if not FServer.Active then 98 | FServer.Active := True; 99 | end; 100 | 101 | procedure TMainForm.StopServerActionExecute(Sender: TObject); 102 | begin 103 | // Turn off the server. 104 | FServer.Active := False; 105 | FServer.Free; 106 | 107 | // Clear all the previously registered metrics to restart from scratch. 108 | TCollectorRegistry.DefaultRegistry.Clear; 109 | end; 110 | 111 | end. 112 | -------------------------------------------------------------------------------- /Samples/Starter-WiRL/Server.Resources.Metrics.pas: -------------------------------------------------------------------------------- 1 | unit Server.Resources.Metrics; 2 | 3 | interface 4 | 5 | uses 6 | WiRL.Core.Attributes, 7 | WiRL.Core.MessageBody.Default, 8 | WiRL.http.Accept.MediaType; 9 | 10 | type 11 | 12 | { TMetricsResource } 13 | 14 | [Path('/metrics')] 15 | TMetricsResource = class 16 | public 17 | [GET, Produces(TMediaType.TEXT_PLAIN)] 18 | function GetMetrics: string; 19 | end; 20 | 21 | implementation 22 | 23 | uses 24 | Prometheus.Exposers.Text, 25 | Prometheus.Registry, 26 | WiRL.Core.Registry; 27 | 28 | { TMetricsResource } 29 | 30 | function TMetricsResource.GetMetrics: string; 31 | begin 32 | // Export the metrics using Prometheus text format. 33 | var LExposer := TTextExposer.Create; 34 | try 35 | Result := LExposer.Render(TCollectorRegistry.DefaultRegistry.Collect); 36 | finally 37 | LExposer.Free; 38 | end; 39 | end; 40 | 41 | initialization 42 | TWiRLResourceRegistry.Instance.RegisterResource; 43 | 44 | end. 45 | -------------------------------------------------------------------------------- /Samples/Starter-WiRL/Server.Resources.Samples.pas: -------------------------------------------------------------------------------- 1 | unit Server.Resources.Samples; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | WiRL.Core.Attributes, 8 | WiRL.Core.MessageBody.Default, 9 | WiRL.http.Core, 10 | WiRL.http.Accept.MediaType; 11 | 12 | type 13 | 14 | { TSampleResource } 15 | 16 | [Path('/samples')] 17 | TSampleResource = class 18 | public 19 | [GET] 20 | [Produces(TMediaType.TEXT_PLAIN)] 21 | function Ping(): string; 22 | end; 23 | 24 | implementation 25 | 26 | uses 27 | WiRL.Core.Registry, 28 | Prometheus.Collectors.Counter, 29 | Prometheus.Registry; 30 | 31 | { TSampleResource } 32 | 33 | function TSampleResource.Ping: string; 34 | begin 35 | // Increments the "counter" metric value with label values. 36 | TCollectorRegistry.DefaultRegistry 37 | .GetCollector('http_requests_count') 38 | .Labels(['/ping', IntToStr(TWiRLHttpStatus.OK)]) // ['path', 'status'] 39 | .Inc(); 40 | 41 | // Sends a sample response to the client. 42 | Result := 'pong'; 43 | end; 44 | 45 | initialization 46 | TWiRLResourceRegistry.Instance.RegisterResource; 47 | 48 | end. 49 | -------------------------------------------------------------------------------- /Samples/Starter-WiRL/Starter.Sample.WiRL.dpr: -------------------------------------------------------------------------------- 1 | program Starter.Sample.WiRL; 2 | 3 | uses 4 | Forms, 5 | Server.Forms.Main in 'Server.Forms.Main.pas' {MainForm}, 6 | Server.Resources.Samples in 'Server.Resources.Samples.pas', 7 | Server.Resources.Metrics in 'Server.Resources.Metrics.pas'; 8 | 9 | {$R *.res} 10 | 11 | begin 12 | ReportMemoryLeaksOnShutdown := True; 13 | Application.Initialize; 14 | Application.MainFormOnTaskbar := True; 15 | Application.CreateForm(TMainForm, MainForm); 16 | Application.Run; 17 | end. -------------------------------------------------------------------------------- /Samples/Starter-WiRL/Starter.Sample.WiRL.dproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {F9FFFB8A-8DD0-4212-BDA6-3D93DA044B95} 4 | Starter.Sample.WiRL.dpr 5 | True 6 | Debug 7 | 1 8 | Application 9 | VCL 10 | 19.5 11 | Win32 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | Base 19 | true 20 | 21 | 22 | true 23 | Base 24 | true 25 | 26 | 27 | true 28 | Base 29 | true 30 | 31 | 32 | true 33 | Cfg_1 34 | true 35 | true 36 | 37 | 38 | true 39 | Base 40 | true 41 | 42 | 43 | true 44 | Cfg_2 45 | true 46 | true 47 | 48 | 49 | true 50 | Cfg_2 51 | true 52 | true 53 | 54 | 55 | ./$(Platform)/$(Config) 56 | ./$(Platform)/$(Config) 57 | ..\Prometheus.Client;$(DCC_UnitSearchPath) 58 | 00400000 59 | Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) 60 | 1040 61 | false 62 | Starter_Sample_WiRL 63 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= 64 | false 65 | false 66 | false 67 | false 68 | $(BDS)\bin\delphi_PROJECTICON.ico 69 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png 70 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png 71 | 72 | 73 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png 74 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png 75 | true 76 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 77 | 1033 78 | $(BDS)\bin\default_app.manifest 79 | true 80 | CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName) 81 | 82 | 83 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) 84 | Debug 85 | true 86 | CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= 87 | 1033 88 | $(BDS)\bin\default_app.manifest 89 | true 90 | 91 | 92 | RELEASE;$(DCC_Define) 93 | 0 94 | 0 95 | false 96 | 97 | 98 | true 99 | PerMonitorV2 100 | 101 | 102 | true 103 | DEBUG;$(DCC_Define) 104 | false 105 | 106 | 107 | Debug 108 | true 109 | true 110 | 1033 111 | CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) 112 | none 113 | 114 | 115 | true 116 | PerMonitorV2 117 | 118 | 119 | 120 | MainSource 121 | 122 | 123 |
MainForm
124 |
125 | 126 | 127 | 128 | Base 129 | 130 | 131 | Cfg_1 132 | Base 133 | 134 | 135 | Cfg_2 136 | Base 137 | 138 |
139 | 140 | Delphi.Personality.12 141 | 142 | 143 | 144 | 145 | Starter.Sample.WiRL.dpr 146 | 147 | 148 | Microsoft Office 2000 Sample Automation Server Wrapper Components 149 | Microsoft Office XP Sample Automation Server Wrapper Components 150 | 151 | 152 | 153 | True 154 | False 155 | 156 | 157 | 158 | 159 | 160 | Starter_Sample_WiRL.exe 161 | true 162 | 163 | 164 | 165 | 166 | 1 167 | 168 | 169 | Contents\MacOS 170 | 1 171 | 172 | 173 | 0 174 | 175 | 176 | 177 | 178 | classes 179 | 64 180 | 181 | 182 | classes 183 | 64 184 | 185 | 186 | 187 | 188 | res\xml 189 | 1 190 | 191 | 192 | res\xml 193 | 1 194 | 195 | 196 | 197 | 198 | library\lib\armeabi-v7a 199 | 1 200 | 201 | 202 | 203 | 204 | library\lib\armeabi 205 | 1 206 | 207 | 208 | library\lib\armeabi 209 | 1 210 | 211 | 212 | 213 | 214 | library\lib\armeabi-v7a 215 | 1 216 | 217 | 218 | 219 | 220 | library\lib\mips 221 | 1 222 | 223 | 224 | library\lib\mips 225 | 1 226 | 227 | 228 | 229 | 230 | library\lib\armeabi-v7a 231 | 1 232 | 233 | 234 | library\lib\arm64-v8a 235 | 1 236 | 237 | 238 | 239 | 240 | library\lib\armeabi-v7a 241 | 1 242 | 243 | 244 | 245 | 246 | res\drawable 247 | 1 248 | 249 | 250 | res\drawable 251 | 1 252 | 253 | 254 | 255 | 256 | res\values 257 | 1 258 | 259 | 260 | res\values 261 | 1 262 | 263 | 264 | 265 | 266 | res\values-v21 267 | 1 268 | 269 | 270 | res\values-v21 271 | 1 272 | 273 | 274 | 275 | 276 | res\values 277 | 1 278 | 279 | 280 | res\values 281 | 1 282 | 283 | 284 | 285 | 286 | res\drawable 287 | 1 288 | 289 | 290 | res\drawable 291 | 1 292 | 293 | 294 | 295 | 296 | res\drawable-xxhdpi 297 | 1 298 | 299 | 300 | res\drawable-xxhdpi 301 | 1 302 | 303 | 304 | 305 | 306 | res\drawable-xxxhdpi 307 | 1 308 | 309 | 310 | res\drawable-xxxhdpi 311 | 1 312 | 313 | 314 | 315 | 316 | res\drawable-ldpi 317 | 1 318 | 319 | 320 | res\drawable-ldpi 321 | 1 322 | 323 | 324 | 325 | 326 | res\drawable-mdpi 327 | 1 328 | 329 | 330 | res\drawable-mdpi 331 | 1 332 | 333 | 334 | 335 | 336 | res\drawable-hdpi 337 | 1 338 | 339 | 340 | res\drawable-hdpi 341 | 1 342 | 343 | 344 | 345 | 346 | res\drawable-xhdpi 347 | 1 348 | 349 | 350 | res\drawable-xhdpi 351 | 1 352 | 353 | 354 | 355 | 356 | res\drawable-mdpi 357 | 1 358 | 359 | 360 | res\drawable-mdpi 361 | 1 362 | 363 | 364 | 365 | 366 | res\drawable-hdpi 367 | 1 368 | 369 | 370 | res\drawable-hdpi 371 | 1 372 | 373 | 374 | 375 | 376 | res\drawable-xhdpi 377 | 1 378 | 379 | 380 | res\drawable-xhdpi 381 | 1 382 | 383 | 384 | 385 | 386 | res\drawable-xxhdpi 387 | 1 388 | 389 | 390 | res\drawable-xxhdpi 391 | 1 392 | 393 | 394 | 395 | 396 | res\drawable-xxxhdpi 397 | 1 398 | 399 | 400 | res\drawable-xxxhdpi 401 | 1 402 | 403 | 404 | 405 | 406 | res\drawable-small 407 | 1 408 | 409 | 410 | res\drawable-small 411 | 1 412 | 413 | 414 | 415 | 416 | res\drawable-normal 417 | 1 418 | 419 | 420 | res\drawable-normal 421 | 1 422 | 423 | 424 | 425 | 426 | res\drawable-large 427 | 1 428 | 429 | 430 | res\drawable-large 431 | 1 432 | 433 | 434 | 435 | 436 | res\drawable-xlarge 437 | 1 438 | 439 | 440 | res\drawable-xlarge 441 | 1 442 | 443 | 444 | 445 | 446 | res\values 447 | 1 448 | 449 | 450 | res\values 451 | 1 452 | 453 | 454 | 455 | 456 | 1 457 | 458 | 459 | Contents\MacOS 460 | 1 461 | 462 | 463 | 0 464 | 465 | 466 | 467 | 468 | Contents\MacOS 469 | 1 470 | .framework 471 | 472 | 473 | Contents\MacOS 474 | 1 475 | .framework 476 | 477 | 478 | Contents\MacOS 479 | 1 480 | .framework 481 | 482 | 483 | 0 484 | 485 | 486 | 487 | 488 | 1 489 | .dylib 490 | 491 | 492 | 1 493 | .dylib 494 | 495 | 496 | 1 497 | .dylib 498 | 499 | 500 | Contents\MacOS 501 | 1 502 | .dylib 503 | 504 | 505 | Contents\MacOS 506 | 1 507 | .dylib 508 | 509 | 510 | Contents\MacOS 511 | 1 512 | .dylib 513 | 514 | 515 | 0 516 | .dll;.bpl 517 | 518 | 519 | 520 | 521 | 1 522 | .dylib 523 | 524 | 525 | 1 526 | .dylib 527 | 528 | 529 | 1 530 | .dylib 531 | 532 | 533 | Contents\MacOS 534 | 1 535 | .dylib 536 | 537 | 538 | Contents\MacOS 539 | 1 540 | .dylib 541 | 542 | 543 | Contents\MacOS 544 | 1 545 | .dylib 546 | 547 | 548 | 0 549 | .bpl 550 | 551 | 552 | 553 | 554 | 0 555 | 556 | 557 | 0 558 | 559 | 560 | 0 561 | 562 | 563 | 0 564 | 565 | 566 | 0 567 | 568 | 569 | Contents\Resources\StartUp\ 570 | 0 571 | 572 | 573 | Contents\Resources\StartUp\ 574 | 0 575 | 576 | 577 | Contents\Resources\StartUp\ 578 | 0 579 | 580 | 581 | 0 582 | 583 | 584 | 585 | 586 | 1 587 | 588 | 589 | 1 590 | 591 | 592 | 593 | 594 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 595 | 1 596 | 597 | 598 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 599 | 1 600 | 601 | 602 | 603 | 604 | ..\ 605 | 1 606 | 607 | 608 | ..\ 609 | 1 610 | 611 | 612 | ..\ 613 | 1 614 | 615 | 616 | 617 | 618 | Contents 619 | 1 620 | 621 | 622 | Contents 623 | 1 624 | 625 | 626 | Contents 627 | 1 628 | 629 | 630 | 631 | 632 | Contents\Resources 633 | 1 634 | 635 | 636 | Contents\Resources 637 | 1 638 | 639 | 640 | Contents\Resources 641 | 1 642 | 643 | 644 | 645 | 646 | library\lib\armeabi-v7a 647 | 1 648 | 649 | 650 | library\lib\arm64-v8a 651 | 1 652 | 653 | 654 | 1 655 | 656 | 657 | 1 658 | 659 | 660 | 1 661 | 662 | 663 | 1 664 | 665 | 666 | Contents\MacOS 667 | 1 668 | 669 | 670 | Contents\MacOS 671 | 1 672 | 673 | 674 | Contents\MacOS 675 | 1 676 | 677 | 678 | 0 679 | 680 | 681 | 682 | 683 | library\lib\armeabi-v7a 684 | 1 685 | 686 | 687 | 688 | 689 | 1 690 | 691 | 692 | 1 693 | 694 | 695 | 696 | 697 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 698 | 1 699 | 700 | 701 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 702 | 1 703 | 704 | 705 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 706 | 1 707 | 708 | 709 | 710 | 711 | ..\ 712 | 1 713 | 714 | 715 | ..\ 716 | 1 717 | 718 | 719 | ..\ 720 | 1 721 | 722 | 723 | 724 | 725 | 1 726 | 727 | 728 | 1 729 | 730 | 731 | 1 732 | 733 | 734 | 735 | 736 | ..\$(PROJECTNAME).launchscreen 737 | 64 738 | 739 | 740 | ..\$(PROJECTNAME).launchscreen 741 | 64 742 | 743 | 744 | 745 | 746 | 1 747 | 748 | 749 | 1 750 | 751 | 752 | 1 753 | 754 | 755 | 756 | 757 | Assets 758 | 1 759 | 760 | 761 | Assets 762 | 1 763 | 764 | 765 | 766 | 767 | Assets 768 | 1 769 | 770 | 771 | Assets 772 | 1 773 | 774 | 775 | 776 | 777 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 778 | 1 779 | 780 | 781 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 782 | 1 783 | 784 | 785 | 786 | 787 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 788 | 1 789 | 790 | 791 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 792 | 1 793 | 794 | 795 | 796 | 797 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 798 | 1 799 | 800 | 801 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 802 | 1 803 | 804 | 805 | 806 | 807 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 808 | 1 809 | 810 | 811 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 812 | 1 813 | 814 | 815 | 816 | 817 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 818 | 1 819 | 820 | 821 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 822 | 1 823 | 824 | 825 | 826 | 827 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 828 | 1 829 | 830 | 831 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 832 | 1 833 | 834 | 835 | 836 | 837 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 838 | 1 839 | 840 | 841 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 842 | 1 843 | 844 | 845 | 846 | 847 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 848 | 1 849 | 850 | 851 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 852 | 1 853 | 854 | 855 | 856 | 857 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 858 | 1 859 | 860 | 861 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 862 | 1 863 | 864 | 865 | 866 | 867 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 868 | 1 869 | 870 | 871 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 872 | 1 873 | 874 | 875 | 876 | 877 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 878 | 1 879 | 880 | 881 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 882 | 1 883 | 884 | 885 | 886 | 887 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 888 | 1 889 | 890 | 891 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 892 | 1 893 | 894 | 895 | 896 | 897 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 898 | 1 899 | 900 | 901 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 902 | 1 903 | 904 | 905 | 906 | 907 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 908 | 1 909 | 910 | 911 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 912 | 1 913 | 914 | 915 | 916 | 917 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 918 | 1 919 | 920 | 921 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 922 | 1 923 | 924 | 925 | 926 | 927 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 928 | 1 929 | 930 | 931 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 932 | 1 933 | 934 | 935 | 936 | 937 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 938 | 1 939 | 940 | 941 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 942 | 1 943 | 944 | 945 | 946 | 947 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 948 | 1 949 | 950 | 951 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 952 | 1 953 | 954 | 955 | 956 | 957 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 958 | 1 959 | 960 | 961 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 962 | 1 963 | 964 | 965 | 966 | 967 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 968 | 1 969 | 970 | 971 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 972 | 1 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | 12 990 | 991 | 992 | 993 | 994 |
995 | -------------------------------------------------------------------------------- /Samples/Starter-WiRL/Starter.Sample.WiRL.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcobreveglieri/prometheus-client-delphi/f3bb03adc4f9cf92ef7978f296943e40437a05b7/Samples/Starter-WiRL/Starter.Sample.WiRL.res -------------------------------------------------------------------------------- /Samples/Starter-WiRL/Starter.Sample.WiRLApp.dpr: -------------------------------------------------------------------------------- 1 | program Starter.Sample.WiRLApp; 2 | 3 | uses 4 | Forms, 5 | Server.Forms.Main in 'Server.Forms.Main.pas' {MainForm}, 6 | Server.Resources.Samples in 'Server.Resources.Samples.pas', 7 | Server.Resources.Metrics in 'Server.Resources.Metrics.pas'; 8 | 9 | {$R *.res} 10 | 11 | begin 12 | ReportMemoryLeaksOnShutdown := True; 13 | Application.Initialize; 14 | Application.MainFormOnTaskbar := True; 15 | Application.CreateForm(TMainForm, MainForm); 16 | Application.Run; 17 | end. -------------------------------------------------------------------------------- /Source/Prometheus.Client.Core.dpk: -------------------------------------------------------------------------------- 1 | package Prometheus.Client.Core; 2 | 3 | {$R *.res} 4 | {$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} 5 | {$ALIGN 8} 6 | {$ASSERTIONS ON} 7 | {$BOOLEVAL OFF} 8 | {$DEBUGINFO OFF} 9 | {$EXTENDEDSYNTAX ON} 10 | {$IMPORTEDDATA ON} 11 | {$IOCHECKS ON} 12 | {$LOCALSYMBOLS ON} 13 | {$LONGSTRINGS ON} 14 | {$OPENSTRINGS ON} 15 | {$OPTIMIZATION OFF} 16 | {$OVERFLOWCHECKS ON} 17 | {$RANGECHECKS ON} 18 | {$REFERENCEINFO ON} 19 | {$SAFEDIVIDE OFF} 20 | {$STACKFRAMES ON} 21 | {$TYPEDADDRESS OFF} 22 | {$VARSTRINGCHECKS ON} 23 | {$WRITEABLECONST OFF} 24 | {$MINENUMSIZE 1} 25 | {$IMAGEBASE $400000} 26 | {$DEFINE DEBUG} 27 | {$ENDIF IMPLICITBUILDING} 28 | {$IMPLICITBUILD ON} 29 | 30 | requires 31 | rtl; 32 | 33 | contains 34 | Prometheus.Collector in 'Prometheus.Collector.pas', 35 | Prometheus.Collectors.Counter in 'Prometheus.Collectors.Counter.pas', 36 | Prometheus.Collectors.Gauge in 'Prometheus.Collectors.Gauge.pas', 37 | Prometheus.Labels in 'Prometheus.Labels.pas', 38 | Prometheus.Metrics in 'Prometheus.Metrics.pas', 39 | Prometheus.Registry in 'Prometheus.Registry.pas', 40 | Prometheus.Exposers.Text in 'Prometheus.Exposers.Text.pas', 41 | Prometheus.Resources in 'Prometheus.Resources.pas', 42 | Prometheus.Samples in 'Prometheus.Samples.pas', 43 | Prometheus.SimpleCollector in 'Prometheus.SimpleCollector.pas', 44 | Prometheus.Collectors.Histogram in 'Prometheus.Collectors.Histogram.pas'; 45 | 46 | end. 47 | -------------------------------------------------------------------------------- /Source/Prometheus.Client.Core.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcobreveglieri/prometheus-client-delphi/f3bb03adc4f9cf92ef7978f296943e40437a05b7/Source/Prometheus.Client.Core.res -------------------------------------------------------------------------------- /Source/Prometheus.Collector.pas: -------------------------------------------------------------------------------- 1 | unit Prometheus.Collector; 2 | 3 | interface 4 | 5 | uses 6 | Prometheus.Samples; 7 | 8 | type 9 | 10 | { TCollector } 11 | 12 | /// 13 | /// This is the base class for all the collector types. 14 | /// Each collector is scraped for metrics and can be registered at one 15 | /// ore more registries. 16 | /// 17 | TCollector = class abstract 18 | public 19 | /// 20 | /// Collects all the metrics and the samples from this collector. 21 | /// 22 | function Collect: TArray; virtual; abstract; 23 | /// 24 | /// Gets all the metric names that are part of this collector. 25 | /// 26 | function GetNames: TArray; virtual; abstract; 27 | end; 28 | 29 | implementation 30 | 31 | end. 32 | -------------------------------------------------------------------------------- /Source/Prometheus.Collectors.Counter.pas: -------------------------------------------------------------------------------- 1 | unit Prometheus.Collectors.Counter; 2 | 3 | interface 4 | 5 | uses 6 | Prometheus.Labels, 7 | Prometheus.Samples, 8 | Prometheus.SimpleCollector; 9 | 10 | type 11 | 12 | { TCounterChild } 13 | 14 | /// 15 | /// Represents a child of a counter assigned to specific label values. 16 | /// 17 | TCounterChild = class 18 | strict private 19 | FLock: TObject; 20 | FValue: Double; 21 | public 22 | /// 23 | /// Creates a new instance of this counter collector child. 24 | /// 25 | constructor Create; 26 | /// 27 | /// Performs object cleanup releasing all the owned instances. 28 | /// 29 | destructor Destroy; override; 30 | /// 31 | /// Increases this counter child by the amount provided. 32 | /// 33 | procedure Inc(const AAmount: Double = 1); 34 | /// 35 | /// Returns the current value of this counter child. 36 | /// 37 | property Value: Double read FValue; 38 | end; 39 | 40 | { TCounter } 41 | 42 | /// 43 | /// A counter is a metric that represents a single monotonically increasing 44 | /// counter whose value can only increase or be reset to zero on restart. 45 | /// 46 | /// 47 | /// You can tipically use a counter to represent the number of requests 48 | /// served, tasks completed, or errors. Do not use a counter to expose a 49 | /// value that can decrease. For example, do not use a counter for the number 50 | /// of currently running processes; use a gauge instead. 51 | /// 52 | TCounter = class (TSimpleCollector) 53 | strict private 54 | function GetValue: Double; 55 | strict protected 56 | function CreateChild: TCounterChild; override; 57 | public 58 | /// 59 | /// Collects all the metrics and the samples from this collector. 60 | /// 61 | function Collect: TArray; override; 62 | /// 63 | /// Increases the default (unlabelled) counter by the amount provided. 64 | /// 65 | procedure Inc(const AAmount: Double = 1); 66 | /// 67 | /// Gets all the metric names that are part of this collector. 68 | /// 69 | function GetNames: TArray; override; 70 | /// 71 | /// Returns the current value of the default (unlabelled) counter. 72 | /// 73 | property Value: Double read GetValue; 74 | end; 75 | 76 | implementation 77 | 78 | uses 79 | System.SysUtils, 80 | Prometheus.Resources; 81 | 82 | { TCounterChild } 83 | 84 | constructor TCounterChild.Create; 85 | begin 86 | inherited Create; 87 | FLock := TObject.Create; 88 | end; 89 | 90 | destructor TCounterChild.Destroy; 91 | begin 92 | if Assigned(FLock) then 93 | FreeAndNil(FLock); 94 | inherited Destroy; 95 | end; 96 | 97 | procedure TCounterChild.Inc(const AAmount: Double); 98 | begin 99 | TMonitor.Enter(FLock); 100 | try 101 | if AAmount <= 0 then 102 | raise EArgumentOutOfRangeException.Create(StrErrAmountLessThanZero); 103 | FValue := FValue + AAmount; 104 | finally 105 | TMonitor.Exit(FLock); 106 | end; 107 | end; 108 | 109 | { TCounter } 110 | 111 | function TCounter.Collect: TArray; 112 | begin 113 | TMonitor.Enter(Lock); 114 | try 115 | SetLength(Result, 1); 116 | var LMetric := PMetricSamples(@Result[0]); 117 | LMetric^.MetricName := Self.Name; 118 | LMetric^.MetricHelp := Self.Help; 119 | LMetric^.MetricType := TMetricType.mtCounter; 120 | SetLength(LMetric^.Samples, ChildrenCount); 121 | var LIndex := 0; 122 | EnumChildren( 123 | procedure (const ALabelValues: TLabelValues; const AChild: TCounterChild) 124 | begin 125 | var LSample := PSample(@LMetric^.Samples[LIndex]); 126 | LSample^.MetricName := Self.Name; 127 | LSample^.LabelNames := Self.LabelNames; 128 | LSample^.LabelValues := ALabelValues; 129 | LSample^.Value := AChild.Value; 130 | System.Inc(LIndex); 131 | end 132 | ); 133 | finally 134 | TMonitor.Exit(Lock); 135 | end; 136 | end; 137 | 138 | function TCounter.CreateChild: TCounterChild; 139 | begin 140 | Result := TCounterChild.Create(); 141 | end; 142 | 143 | function TCounter.GetNames: TArray; 144 | begin 145 | Result := [Name]; 146 | end; 147 | 148 | function TCounter.GetValue: Double; 149 | begin 150 | Result := GetNoLabelChild.Value; 151 | end; 152 | 153 | procedure TCounter.Inc(const AAmount: Double); 154 | begin 155 | GetNoLabelChild.Inc(AAmount); 156 | end; 157 | 158 | end. 159 | -------------------------------------------------------------------------------- /Source/Prometheus.Collectors.Gauge.pas: -------------------------------------------------------------------------------- 1 | unit Prometheus.Collectors.Gauge; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | Prometheus.Labels, 8 | Prometheus.Samples, 9 | Prometheus.SimpleCollector; 10 | 11 | type 12 | 13 | { TGaugeChild } 14 | 15 | /// 16 | /// Represents a child of a gauge assigned to specific label values. 17 | /// 18 | TGaugeChild = class 19 | strict private 20 | FLock: TObject; 21 | FValue: Double; 22 | public 23 | /// 24 | /// Creates a new instance of this gauge collector child. 25 | /// 26 | constructor Create; 27 | /// 28 | /// Performs object cleanup releasing all the owned instances. 29 | /// 30 | destructor Destroy; override; 31 | /// 32 | /// Decreases this gauge child by the amount provided. 33 | /// 34 | procedure Dec(const AAmount: Double = 1); 35 | /// 36 | /// Increases this gauge child by the amount provided. 37 | /// 38 | procedure Inc(const AAmount: Double = 1); 39 | /// 40 | /// Sets the value of this gauge child to the duration 41 | /// of the execution of the specified function. 42 | /// 43 | procedure SetDuration(const AProc: TProc); 44 | /// 45 | /// Sets the value of this gauge child to the specified amount. 46 | /// 47 | procedure SetTo(const AValue: Double); 48 | /// 49 | /// Sets the value of this gauge child to the current time as a Unix. 50 | /// 51 | procedure SetToCurrentTime; 52 | /// 53 | /// Returns the current value of this gauge child. 54 | /// 55 | property Value: Double read FValue; 56 | end; 57 | 58 | { TGauge } 59 | 60 | /// 61 | /// A gauge is a metric that represents a single numerical value that can 62 | /// arbitrarily go up and down. 63 | /// 64 | /// 65 | /// Gauges are typically used for measured values like temperatures or 66 | /// current memory usage, but also "counts" that can go up and down, 67 | /// like the number of concurrent requests. 68 | /// 69 | TGauge = class (TSimpleCollector) 70 | strict private 71 | function GetValue: Double; 72 | strict protected 73 | function CreateChild: TGaugeChild; override; 74 | public 75 | /// 76 | /// Collects all the metrics and the samples from this collector. 77 | /// 78 | function Collect: TArray; override; 79 | /// 80 | /// Gets all the metric names that are part of this collector. 81 | /// 82 | function GetNames: TArray; override; 83 | /// 84 | /// Decreases the default (unlabelled) gauge by the amount provided. 85 | /// 86 | procedure Dec(const AAmount: Double = 1); 87 | /// 88 | /// Increases the default (unlabelled) gauge by the amount provided. 89 | /// 90 | procedure Inc(const AAmount: Double = 1); 91 | /// 92 | /// Set the value of the default (unlabelled) gauge to the 93 | /// duration of the execution of the specified function. 94 | /// 95 | procedure SetDuration(const AProc: TProc); 96 | /// 97 | /// Sets the value of the default (unlabelled) gauge to the specified amount. 98 | /// 99 | procedure SetTo(const AValue: Double); 100 | /// 101 | /// Sets the value of the default (unlabelled) gauge to the current time as a Unix. 102 | /// 103 | procedure SetToCurrentTime; 104 | /// 105 | /// Returns the current value of the default (unlabelled) gauge. 106 | /// 107 | property Value: Double read GetValue; 108 | end; 109 | 110 | implementation 111 | 112 | { TGaugeChild } 113 | 114 | uses 115 | System.DateUtils, 116 | System.Diagnostics, 117 | Prometheus.Resources; 118 | 119 | constructor TGaugeChild.Create; 120 | begin 121 | inherited Create; 122 | FLock := TObject.Create; 123 | end; 124 | 125 | destructor TGaugeChild.Destroy; 126 | begin 127 | if Assigned(FLock) then 128 | FreeAndNil(FLock); 129 | inherited Destroy; 130 | end; 131 | 132 | procedure TGaugeChild.Dec(const AAmount: Double); 133 | begin 134 | TMonitor.Enter(FLock); 135 | try 136 | FValue := FValue - AAmount; 137 | finally 138 | TMonitor.Exit(FLock); 139 | end; 140 | end; 141 | 142 | procedure TGaugeChild.Inc(const AAmount: Double); 143 | begin 144 | TMonitor.Enter(FLock); 145 | try 146 | FValue := FValue + AAmount; 147 | finally 148 | TMonitor.Exit(FLock); 149 | end; 150 | end; 151 | 152 | procedure TGaugeChild.SetDuration(const AProc: TProc); 153 | begin 154 | TMonitor.Enter(FLock); 155 | try 156 | if not Assigned(AProc) then 157 | raise EArgumentNilException.Create(StrErrNullProcReference); 158 | var LStopWatch := TStopwatch.StartNew; 159 | try 160 | AProc; 161 | finally 162 | LStopWatch.Stop; 163 | FValue := LStopWatch.Elapsed.TotalMilliseconds; 164 | end; 165 | finally 166 | TMonitor.Exit(FLock); 167 | end; 168 | end; 169 | 170 | procedure TGaugeChild.SetTo(const AValue: Double); 171 | begin 172 | TMonitor.Enter(FLock); 173 | try 174 | FValue := AValue; 175 | finally 176 | TMonitor.Exit(FLock); 177 | end; 178 | end; 179 | 180 | procedure TGaugeChild.SetToCurrentTime; 181 | begin 182 | TMonitor.Enter(FLock); 183 | try 184 | FValue := TDateTime.NowUTC.ToUnix(); 185 | finally 186 | TMonitor.Exit(FLock); 187 | end; 188 | end; 189 | 190 | { TGauge } 191 | 192 | function TGauge.Collect: TArray; 193 | begin 194 | TMonitor.Enter(Lock); 195 | try 196 | SetLength(Result, 1); 197 | var LMetric := PMetricSamples(@Result[0]); 198 | LMetric^.MetricName := Self.Name; 199 | LMetric^.MetricHelp := Self.Help; 200 | LMetric^.MetricType := TMetricType.mtGauge; 201 | SetLength(LMetric^.Samples, ChildrenCount); 202 | var LIndex := 0; 203 | EnumChildren( 204 | procedure (const ALabelValues: TLabelValues; const AChild: TGaugeChild) 205 | begin 206 | var LSample := PSample(@LMetric^.Samples[LIndex]); 207 | LSample^.MetricName := Self.Name; 208 | LSample^.LabelNames := Self.LabelNames; 209 | LSample^.LabelValues := ALabelValues; 210 | LSample^.Value := AChild.Value; 211 | System.Inc(LIndex); 212 | end 213 | ); 214 | finally 215 | TMonitor.Exit(Lock); 216 | end; 217 | end; 218 | 219 | function TGauge.CreateChild: TGaugeChild; 220 | begin 221 | Result := TGaugeChild.Create(); 222 | end; 223 | 224 | procedure TGauge.Dec(const AAmount: Double); 225 | begin 226 | GetNoLabelChild.Dec(AAmount); 227 | end; 228 | 229 | function TGauge.GetNames: TArray; 230 | begin 231 | Result := [Name]; 232 | end; 233 | 234 | function TGauge.GetValue: Double; 235 | begin 236 | Result := GetNoLabelChild.Value; 237 | end; 238 | 239 | procedure TGauge.Inc(const AAmount: Double); 240 | begin 241 | GetNoLabelChild.Inc(AAmount); 242 | end; 243 | 244 | procedure TGauge.SetDuration(const AProc: TProc); 245 | begin 246 | GetNoLabelChild.SetDuration(AProc); 247 | end; 248 | 249 | procedure TGauge.SetTo(const AValue: Double); 250 | begin 251 | GetNoLabelChild.SetTo(AValue); 252 | end; 253 | 254 | procedure TGauge.SetToCurrentTime; 255 | begin 256 | GetNoLabelChild.SetToCurrentTime; 257 | end; 258 | 259 | end. 260 | -------------------------------------------------------------------------------- /Source/Prometheus.Collectors.Histogram.pas: -------------------------------------------------------------------------------- 1 | unit Prometheus.Collectors.Histogram; 2 | 3 | interface 4 | 5 | uses 6 | Prometheus.Labels, 7 | Prometheus.Samples, 8 | Prometheus.SimpleCollector; 9 | 10 | type 11 | 12 | { TBuckets } 13 | 14 | TBuckets = TArray; 15 | 16 | { Consts } 17 | 18 | const 19 | 20 | /// 21 | /// Default histogram buckets. 22 | /// 23 | DEFAULT_BUCKETS: TBuckets = [ 24 | 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 25 | 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, 26 | INFINITE 27 | ]; 28 | 29 | type 30 | 31 | { Forward class declarations } 32 | 33 | THistogram = class; 34 | 35 | { THistogramChild } 36 | 37 | /// 38 | /// Represents a histogram data collection for a given label combination. 39 | /// 40 | /// 41 | /// Includes the individual "bucket" values for the specified labels. 42 | /// 43 | THistogramChild = class 44 | strict private 45 | FOwner: THistogram; 46 | FLock: TObject; 47 | FCount: Int64; 48 | FSum: Double; 49 | FValues: TArray; 50 | public 51 | /// 52 | /// Creates a new instance of this histogram collector child. 53 | /// 54 | constructor Create(AOwner: THistogram); 55 | /// 56 | /// Performs object cleanup releasing all the owned instances. 57 | /// 58 | destructor Destroy; override; 59 | /// 60 | /// Collects all the samples of this histogram child. 61 | /// 62 | procedure CollectChild(var ASamples: TArray; 63 | const ALabelValues: TLabelValues); 64 | /// 65 | /// Adds a single observation to the histogram. 66 | /// 67 | /// 68 | /// The buckets are cummulative and any value that is less than - or equal 69 | /// to - the upper bound will increment the bucket. 70 | /// 71 | function Observe(AValue: Double): THistogram; 72 | /// 73 | /// Gets the total number of samples collected for this labelled child. 74 | /// 75 | property Count: Int64 read FCount; 76 | /// 77 | /// Gets the total cumulative value calculated for this labelled child. 78 | /// 79 | property Sum: Double read FSum; 80 | /// 81 | /// The array of cumulative counts for values falling within 82 | /// the intervals specified by the parent (owner) histogram. 83 | /// 84 | property Values: TArray read FValues; 85 | end; 86 | 87 | { TBucketGeneratorFunc } 88 | 89 | /// 90 | /// A function that returns the buckets to be used in histogram metrics. 91 | /// 92 | TBucketGeneratorFunc = reference to function: TBuckets; 93 | 94 | { THistogram } 95 | 96 | /// 97 | /// An histogram is a metric that counts observed values using a number 98 | /// of configurable buckets and expose those ones as individual counter 99 | /// time series. 100 | /// 101 | /// 102 | /// Histograms are tipically used to allow a generic service to record the 103 | /// distribution of a stream of data values into a set of ranged buckets. 104 | /// 105 | THistogram = class(TSimpleCollector) 106 | strict private 107 | FBuckets: TBuckets; 108 | FCount: Int64; 109 | FSum: Double; 110 | function GetValue: Double; 111 | private 112 | procedure IncrementOwner(AValue: Double); 113 | strict protected 114 | function CreateChild: THistogramChild; override; 115 | public 116 | /// 117 | /// Creates a new instance of a histogram collector. 118 | /// 119 | constructor Create(const AName: string; const AHelp: string = ''; 120 | const ABuckets: TBuckets = []; const ALabelNames: TLabelNames = []); reintroduce; overload; 121 | /// 122 | /// Creates a new instance of a histogram collector 123 | /// with an increasing sequence in the bucket. 124 | /// 125 | constructor Create(const AName: string; const AHelp: string; const AStart, AFactor: Double; const ACount: Integer; 126 | const ALabelNames: TLabelNames); reintroduce; overload; 127 | /// 128 | /// Creates a new instance of a histogram collector 129 | /// with a custom sequence of buckets. 130 | /// 131 | /// 132 | /// Generate buckets by passing an appropriate function when calling this constructor. 133 | /// 134 | constructor Create(const AName: string; const AHelp: string; 135 | ABucketGeneratorFunc: TBucketGeneratorFunc; 136 | const ALabelNames: TLabelNames); reintroduce; overload; 137 | /// 138 | /// Adds an observation to the top level histogram (i.e. no labels applied). 139 | /// 140 | /// 141 | function Observe(AValue: Double): THistogram; 142 | /// 143 | /// Collects all the metrics and the samples from this collector. 144 | /// 145 | function Collect: TArray; override; 146 | /// 147 | /// Gets all the metric names that are part of this collector. 148 | /// 149 | function GetNames: TArray; override; 150 | /// 151 | /// Gets an array holding the upper limit of each histogram bucket. 152 | /// 153 | property Buckets: TBuckets read FBuckets; 154 | /// 155 | /// Returns the current count of values belonging to this metric. 156 | /// 157 | property Count: Int64 read FCount; 158 | /// 159 | /// Returns the current sum of values belonging to this metric. 160 | /// 161 | property Sum: Double read FSum; 162 | /// 163 | /// Returns the current value of the default (unlabelled) histogram. 164 | /// 165 | property Value: Double read GetValue; 166 | end; 167 | 168 | implementation 169 | 170 | uses 171 | System.Generics.Collections, 172 | System.StrUtils, 173 | System.SysUtils, 174 | Prometheus.Resources; 175 | 176 | const 177 | RESERVED_LABEL_NAME = 'le'; 178 | 179 | { THistogramChild } 180 | 181 | constructor THistogramChild.Create(AOwner: THistogram); 182 | begin 183 | inherited Create; 184 | if not Assigned(AOwner) then 185 | raise EArgumentNilException.Create(StrErrHistogramOwnerNil); 186 | if Length(AOwner.Buckets) <= 0 then 187 | raise EArgumentException.Create(StrErrHistogramOwnerNoBuckets); 188 | FLock := TObject.Create; 189 | FOwner := AOwner; 190 | SetLength(FValues, Length(AOwner.Buckets)); 191 | end; 192 | 193 | destructor THistogramChild.Destroy; 194 | begin 195 | if Assigned(FLock) then 196 | FreeAndNil(FLock); 197 | inherited Destroy; 198 | end; 199 | 200 | procedure THistogramChild.CollectChild(var ASamples: TArray; 201 | const ALabelValues: TLabelValues); 202 | begin 203 | TMonitor.Enter(FLock); 204 | try 205 | var LStartIndex := Length(ASamples); 206 | SetLength(ASamples, LStartIndex + Length(FOwner.Buckets)); 207 | 208 | var LLabelNames := FOwner.LabelNames; 209 | SetLength(LLabelNames, Length(FOwner.LabelNames) + 1); 210 | LLabelNames[Length(FOwner.LabelNames)] := RESERVED_LABEL_NAME; 211 | 212 | var LLabelValues := ALabelValues; 213 | SetLength(LLabelValues, Length(ALabelValues) + 1); 214 | 215 | for var LBucketIndex := 0 to Length(FOwner.Buckets) - 1 do 216 | begin 217 | var LSample := PSample(@ASamples[LStartIndex + LBucketIndex]); 218 | LSample^.MetricName := FOwner.Name + '_bucket'; 219 | LSample^.LabelNames := LLabelNames; 220 | if FOwner.Buckets[LBucketIndex] < INFINITE then 221 | begin 222 | var LFormatSettings := TFormatSettings.Create; 223 | LFormatSettings.DecimalSeparator := '.'; 224 | LFormatSettings.ThousandSeparator := ','; 225 | LLabelValues[Length(ALabelValues)] := FloatToStr(FOwner.Buckets[LBucketIndex], LFormatSettings) 226 | end 227 | else 228 | LLabelValues[Length(ALabelValues)] := '+Inf'; 229 | LSample^.LabelValues := LLabelValues; 230 | SetLength(LSample^.LabelValues, Length(LLabelValues)); 231 | LSample^.Value := FValues[LBucketIndex]; 232 | end; 233 | finally 234 | TMonitor.Exit(FLock); 235 | end; 236 | end; 237 | 238 | function THistogramChild.Observe(AValue: Double): THistogram; 239 | begin 240 | TMonitor.Enter(FLock); 241 | try 242 | for var LIndex := Length(FValues) - 1 downto 0 do 243 | begin 244 | if AValue <= FOwner.Buckets[LIndex] then 245 | Inc(FValues[LIndex]) 246 | else 247 | Break; 248 | end; 249 | Inc(FCount); 250 | FSum := FSum + AValue; 251 | finally 252 | TMonitor.Exit(FLock); 253 | end; 254 | FOwner.IncrementOwner(AValue); 255 | Result := FOwner; 256 | end; 257 | 258 | { THistogram } 259 | 260 | constructor THistogram.Create(const AName, AHelp: string; 261 | const ABuckets: TBuckets; const ALabelNames: TLabelNames); 262 | begin 263 | if IndexText(RESERVED_LABEL_NAME, ALabelNames) > -1 then 264 | raise EInvalidOpException.CreateFmt('Label name ''%s'' is reserved', [RESERVED_LABEL_NAME]); 265 | if Length(ABuckets) > 0 then 266 | FBuckets := ABuckets 267 | else 268 | FBuckets := DEFAULT_BUCKETS; 269 | TArray.Sort(FBuckets); 270 | var LBucketCount := Length(FBuckets); 271 | if FBuckets[LBucketCount - 1] < INFINITE then 272 | begin 273 | SetLength(FBuckets, LBucketCount + 1); 274 | FBuckets[LBucketCount] := INFINITE; 275 | end; 276 | inherited Create(AName, AHelp, ALabelNames); 277 | end; 278 | 279 | constructor THistogram.Create(const AName, AHelp: string; 280 | ABucketGeneratorFunc: TBucketGeneratorFunc; const ALabelNames: TLabelNames); 281 | begin 282 | Create(AName, AHelp, ABucketGeneratorFunc, ALabelNames); 283 | end; 284 | 285 | constructor THistogram.Create(const AName, AHelp: string; 286 | const AStart, AFactor: Double; const ACount: Integer; 287 | const ALabelNames: TLabelNames); 288 | begin 289 | Create(AName, AHelp, 290 | function (): TBuckets 291 | begin 292 | SetLength(Result, ACount); 293 | var LCurrentValue := AStart; 294 | for var LCurrentIndex := 0 to ACount - 1 do 295 | begin 296 | Result[LCurrentIndex] := LCurrentValue; 297 | LCurrentValue := LCurrentValue * AFactor; 298 | end; 299 | end, 300 | ALabelNames); 301 | end; 302 | 303 | function THistogram.CreateChild: THistogramChild; 304 | begin 305 | Result := THistogramChild.Create(Self); 306 | end; 307 | 308 | function THistogram.Collect: TArray; 309 | begin 310 | TMonitor.Enter(Lock); 311 | try 312 | SetLength(Result, 1); 313 | var LMetric := PMetricSamples(@Result[0]); 314 | LMetric^.MetricName := Self.Name; 315 | LMetric^.MetricHelp := Self.Help; 316 | LMetric^.MetricType := TMetricType.mtHistogram; 317 | LMetric^.MetricSum := FSum; 318 | LMetric^.MetricCount := FCount; 319 | SetLength(LMetric.Samples, 0); // Clear link to previous samples. 320 | EnumChildren( 321 | procedure(const ALabelValues: TLabelValues; const AChild: THistogramChild) 322 | begin 323 | if Length(LabelNames) > 0 then 324 | begin 325 | // Add the top level sum for the child. 326 | var LStartIndex := Length(LMetric.Samples); 327 | SetLength(LMetric.Samples, LStartIndex + 1); 328 | var LSample := PSample(@LMetric.Samples[LStartIndex]); 329 | LSample^.MetricName := Name + '_sum'; 330 | LSample^.LabelNames := LabelNames; 331 | LSample^.LabelValues := ALabelValues; 332 | LSample^.Value := AChild.Sum; 333 | // Add the top level count for the child. 334 | LStartIndex := Length(LMetric.Samples); 335 | SetLength(LMetric.Samples, LStartIndex + 1); 336 | LSample := PSample(@LMetric.Samples[LStartIndex]); 337 | LSample^.MetricName := Name + '_count'; 338 | LSample^.LabelNames := LabelNames; 339 | LSample^.LabelValues := ALabelValues; 340 | LSample^.Value := AChild.Count; 341 | end; 342 | AChild.CollectChild(LMetric^.Samples, ALabelValues); 343 | end 344 | ); 345 | finally 346 | TMonitor.Exit(Lock); 347 | end; 348 | end; 349 | 350 | function THistogram.GetNames: TArray; 351 | begin 352 | Result := [Name]; 353 | end; 354 | 355 | function THistogram.GetValue: Double; 356 | begin 357 | Result := GetNoLabelChild.Sum; 358 | end; 359 | 360 | procedure THistogram.IncrementOwner(AValue: Double); 361 | begin 362 | TMonitor.Enter(Lock); 363 | try 364 | Inc(FCount); 365 | FSum := FSum + AValue; 366 | finally 367 | TMonitor.Exit(Lock); 368 | end; 369 | end; 370 | 371 | function THistogram.Observe(AValue: Double): THistogram; 372 | begin 373 | Result := Self; 374 | TMonitor.Enter(Lock); 375 | try 376 | GetNoLabelChild.Observe(AValue); 377 | finally 378 | TMonitor.Exit(Lock); 379 | end; 380 | end; 381 | 382 | end. 383 | -------------------------------------------------------------------------------- /Source/Prometheus.Exposers.Text.pas: -------------------------------------------------------------------------------- 1 | unit Prometheus.Exposers.Text; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | System.SysUtils, 8 | Prometheus.Samples; 9 | 10 | type 11 | 12 | { TTextExposer } 13 | 14 | /// 15 | /// Provides methods to export metrics using the Prometheus text representation. 16 | /// 17 | TTextExposer = class 18 | strict private 19 | function EscapeToken(const AText: string): string; 20 | function FormatNumber(const AValue: Double): string; 21 | public 22 | /// 23 | /// Renders the specified samples as a string. 24 | /// 25 | function Render(ASamples: TArray): string; overload; 26 | /// 27 | /// Renders the specified samples to a string buffer. 28 | /// 29 | procedure Render(ABuilder: TStringBuilder; ASamples: TArray); overload; 30 | /// 31 | /// Renders the specified samples to a destination stream. 32 | /// 33 | procedure Render(AStream: TStream; ASamples: TArray); overload; 34 | /// 35 | /// Renders the specified samples to a text writer instance. 36 | /// 37 | procedure Render(AWriter: TTextWriter; ASamples: TArray); overload; 38 | end; 39 | 40 | implementation 41 | 42 | uses 43 | System.Math; 44 | 45 | type 46 | 47 | { TTextEncoding } 48 | 49 | /// 50 | /// Represents the UTF-8 encoding that is compliant with Prometheus text format. 51 | /// 52 | TTextEncoding = class(TUTF8Encoding) 53 | public 54 | /// 55 | /// Returns this encoding preamble in a byte array. 56 | /// 57 | function GetPreamble: TBytes; override; 58 | end; 59 | 60 | function TTextEncoding.GetPreamble: TBytes; 61 | begin 62 | SetLength(Result, 0); 63 | end; 64 | 65 | { TTextExposer } 66 | 67 | function TTextExposer.EscapeToken(const AText: string): string; 68 | begin 69 | Result := AText 70 | .Replace('\', '\\') 71 | .Replace(#13#10, '\n') 72 | .Replace(#13, '\n') 73 | .Replace(#10, '\n') 74 | .Replace(#9, '\t') 75 | .Replace('"', '\"'); 76 | end; 77 | 78 | function TTextExposer.FormatNumber(const AValue: Double): string; 79 | const 80 | // This pattern avoids the use of exponential notation 81 | // since it leads to errors when parsed from Prometheus 82 | // using the strconv.ParseFloat() function call in Go 83 | // (see: https://pkg.go.dev/strconv#ParseFloat). 84 | SFormatPattern = '0.######################'; 85 | begin 86 | if AValue.IsNegativeInfinity then 87 | begin 88 | Result := '-Inf'; 89 | Exit; 90 | end; 91 | if AValue.IsPositiveInfinity then 92 | begin 93 | Result := '+Inf'; 94 | Exit; 95 | end; 96 | if AValue.IsNan then 97 | begin 98 | Result := 'Nan'; 99 | Exit; 100 | end; 101 | var LFormatSettings := TFormatSettings.Create; 102 | LFormatSettings.DecimalSeparator := '.'; 103 | LFormatSettings.ThousandSeparator := ','; 104 | Result := FormatFloat(SFormatPattern, AValue, LFormatSettings); 105 | end; 106 | 107 | function TTextExposer.Render(ASamples: TArray): string; 108 | begin 109 | var LBuffer := TStringBuilder.Create; 110 | try 111 | Render(LBuffer, ASamples); 112 | Result := LBuffer.ToString; 113 | finally 114 | LBuffer.Free; 115 | end; 116 | end; 117 | 118 | procedure TTextExposer.Render(ABuilder: TStringBuilder; ASamples: TArray); 119 | begin 120 | var LWriter := TStringWriter.Create(ABuilder); 121 | try 122 | Render(LWriter, ASamples); 123 | finally 124 | LWriter.Free; 125 | end; 126 | end; 127 | 128 | procedure TTextExposer.Render(AStream: TStream; ASamples: TArray); 129 | begin 130 | var LEncoding := TTextEncoding.Create; 131 | try 132 | var LWriter := TStreamWriter.Create(AStream, LEncoding); 133 | try 134 | Render(LWriter, ASamples); 135 | finally 136 | LWriter.Free; 137 | end; 138 | finally 139 | LEncoding.Free; 140 | end; 141 | end; 142 | 143 | procedure TTextExposer.Render(AWriter: TTextWriter; ASamples: TArray); 144 | begin 145 | for var LMetricSet in ASamples do 146 | begin 147 | if LMetricSet.IsEmpty then 148 | Continue; 149 | 150 | // Metric help 151 | AWriter.Write('# HELP'); 152 | AWriter.Write(' '); 153 | AWriter.Write(LMetricSet.MetricName); 154 | AWriter.Write(' '); 155 | AWriter.Write(EscapeToken(LMetricSet.MetricHelp)); 156 | if not LMetricSet.MetricHelp.EndsWith('.') then 157 | AWriter.Write('.'); 158 | AWriter.Write(#10); 159 | 160 | // Metric type 161 | AWriter.Write('# TYPE'); 162 | AWriter.Write(' '); 163 | AWriter.Write(LMetricSet.MetricName); 164 | AWriter.Write(' '); 165 | AWriter.Write(StrMetricType[LMetricSet.MetricType]); 166 | AWriter.Write(#10); 167 | 168 | // Metric samples 169 | for var LSample in LMetricSet.Samples do 170 | begin 171 | AWriter.Write(LSample.MetricName); 172 | if LSample.HasLabels then 173 | begin 174 | AWriter.Write('{'); 175 | var LLabelCount := Min(Length(LSample.LabelNames), Length(LSample.LabelValues)); 176 | if LLabelCount <= 0 then 177 | Continue; 178 | for var LLabelIndex := 0 to Pred(LLabelCount) do 179 | begin 180 | if LLabelIndex > 0 then 181 | AWriter.Write(','); 182 | AWriter.Write(LSample.LabelNames[LLabelIndex]); 183 | AWriter.Write('="'); 184 | AWriter.Write(EscapeToken(LSample.LabelValues[LLabelIndex])); 185 | AWriter.Write('"'); 186 | end; 187 | AWriter.Write('}'); 188 | end; 189 | AWriter.Write(' '); 190 | AWriter.Write(FormatNumber(LSample.Value)); 191 | AWriter.Write(#10); 192 | end; 193 | 194 | if LMetricSet.MetricType = TMetricType.mtHistogram then 195 | begin 196 | if LMetricSet.MetricSum > 0.0 then 197 | begin 198 | AWriter.Write(Format('%s_sum %s', [ 199 | LMetricSet.MetricName, 200 | FormatNumber(LMetricSet.MetricSum) 201 | ])); 202 | AWriter.Write(#10); 203 | end; 204 | if LMetricSet.MetricCount > 0.0 then 205 | begin 206 | AWriter.Write(Format('%s_count %s', [ 207 | LMetricSet.MetricName, 208 | IntToStr(LMetricSet.MetricCount) 209 | ])); 210 | AWriter.Write(#10); 211 | end; 212 | end; 213 | end; 214 | end; 215 | 216 | end. 217 | -------------------------------------------------------------------------------- /Source/Prometheus.Labels.pas: -------------------------------------------------------------------------------- 1 | unit Prometheus.Labels; 2 | 3 | interface 4 | 5 | uses 6 | System.Generics.Defaults; 7 | 8 | type 9 | 10 | { TLabelNames } 11 | 12 | /// 13 | /// Represents a set of label names. 14 | /// 15 | TLabelNames = TArray; 16 | 17 | { TLabelValues } 18 | 19 | /// 20 | /// Represents a set of label values. 21 | /// 22 | TLabelValues = TArray; 23 | 24 | { TLabelValidator } 25 | 26 | /// 27 | /// Provides methods to validate label names. 28 | /// 29 | TLabelValidator = class sealed 30 | strict private const 31 | NamePattern: string = '^[a-zA-Z_][a-zA-Z0-9_]*$'; 32 | ReservedPattern: string = '^__.*$'; 33 | public 34 | /// 35 | /// Check if a label name is valid. 36 | /// 37 | class procedure CheckLabel(const AName: string); 38 | /// 39 | /// Check if a set of label names is valid. 40 | /// 41 | class procedure CheckLabels(const ANames: TLabelNames); 42 | end; 43 | 44 | { TLabelNamesEqualityComparer } 45 | 46 | /// 47 | /// Implements an equality comparer for label names. 48 | /// 49 | TLabelNamesEqualityComparer = class (TEqualityComparer) 50 | public 51 | function Equals(const Left, Right: TLabelNames): Boolean; override; 52 | function GetHashCode(const Value: TLabelNames): Integer; override; 53 | end; 54 | 55 | implementation 56 | 57 | uses 58 | System.Hash, 59 | System.RegularExpressions, 60 | System.SysUtils, 61 | Prometheus.Resources; 62 | 63 | { TLabelValidator } 64 | 65 | class procedure TLabelValidator.CheckLabel(const AName: string); 66 | begin 67 | if Length(Trim(AName)) <= 0 then 68 | begin 69 | raise EArgumentException.Create(StrErrEmptyLabelName); 70 | end; 71 | if not TRegEx.IsMatch(AName, NamePattern) then 72 | begin 73 | raise EArgumentException.Create(StrErrInvalidLabelName); 74 | end; 75 | if TRegEx.IsMatch(AName, ReservedPattern) then 76 | begin 77 | raise EArgumentException.Create(StrErrReservedLabelName); 78 | end; 79 | end; 80 | 81 | class procedure TLabelValidator.CheckLabels(const ANames: TLabelNames); 82 | begin 83 | for var LName in ANames do 84 | CheckLabel(LName); 85 | end; 86 | 87 | { TLabelNamesEqualityComparer } 88 | 89 | function TLabelNamesEqualityComparer.Equals( 90 | const Left, Right: TLabelNames): Boolean; 91 | begin 92 | if Length(Left) <> Length(Right) then 93 | begin 94 | Result := False; 95 | Exit; 96 | end; 97 | for var LIndex := 0 to Pred(Length(Left)) do 98 | if not SameText(Left[LIndex], Right[LIndex]) then 99 | begin 100 | Result := False; 101 | Exit; 102 | end; 103 | Result := True; 104 | end; 105 | 106 | function TLabelNamesEqualityComparer.GetHashCode( 107 | const Value: TLabelNames): Integer; 108 | begin 109 | var LText := string.Empty; 110 | for var LIndex := 0 to Pred(Length(Value)) do 111 | LText := LText + LowerCase(Value[LIndex]); 112 | Result := THashFNV1a32.GetHashValue(LText); 113 | end; 114 | 115 | end. 116 | -------------------------------------------------------------------------------- /Source/Prometheus.Metrics.pas: -------------------------------------------------------------------------------- 1 | unit Prometheus.Metrics; 2 | 3 | interface 4 | 5 | type 6 | 7 | { TMetricValidator } 8 | 9 | /// 10 | /// Provides methods to validate metric names. 11 | /// 12 | TMetricValidator = class sealed 13 | strict private const 14 | NamePattern: string = '^[a-zA-Z_:][a-zA-Z0-9_:]*$'; 15 | public 16 | /// 17 | /// Check if a metric name is valid. 18 | /// 19 | class procedure CheckName(const AName: string); 20 | end; 21 | 22 | implementation 23 | 24 | { TMetricValidator } 25 | 26 | uses 27 | System.RegularExpressions, 28 | System.SysUtils, 29 | Prometheus.Resources; 30 | 31 | class procedure TMetricValidator.CheckName(const AName: string); 32 | begin 33 | if Length(AName) <= 0 then 34 | raise EArgumentException.Create(StrErrEmptyMetricName); 35 | if not TRegEx.IsMatch(AName, NamePattern) then 36 | raise EArgumentException.Create(StrErrInvalidMetricName); 37 | end; 38 | 39 | end. 40 | -------------------------------------------------------------------------------- /Source/Prometheus.Registry.pas: -------------------------------------------------------------------------------- 1 | unit Prometheus.Registry; 2 | 3 | interface 4 | 5 | uses 6 | System.Generics.Collections, 7 | Prometheus.Collector, 8 | Prometheus.Samples; 9 | 10 | type 11 | 12 | { TCollectorRegistry } 13 | 14 | /// 15 | /// A collector registry is used to contain and manage collectors 16 | /// and allows one or more of them to be registered. 17 | /// 18 | /// 19 | /// Collectors can be registered to one ore more collector registry. 20 | /// Each registry can be scraped to collect metrics and samples from 21 | /// collectors that are being managed by it. 22 | /// 23 | TCollectorRegistry = class 24 | strict private 25 | FCollectorsToNames: TDictionary>; 26 | FLock: TObject; 27 | FNamesToCollectors: TDictionary; 28 | class var FDefaultRegistry: TCollectorRegistry; 29 | class function GetDefaultRegistry: TCollectorRegistry; static; 30 | public 31 | /// 32 | /// Performs cleanup and release of resources used by this class. 33 | /// 34 | class destructor Finalize; 35 | /// 36 | /// Creates a new collector registry. 37 | /// 38 | constructor Create(AOwnsCollectors: Boolean = True); 39 | /// 40 | /// Performs object cleanup releasing all the owned instances. 41 | /// 42 | destructor Destroy; override; 43 | /// 44 | /// Clears all the registered collectors within this instance. 45 | /// 46 | procedure Clear; 47 | /// 48 | /// Collects all the metrics and their samples from the registered collectors. 49 | /// 50 | function Collect: TArray; 51 | /// 52 | /// Gets a collector of the specified type by its name. 53 | /// 54 | function GetCollector(const AName: string): T; 55 | /// 56 | /// Check if a collector is registered with the specified name. 57 | /// 58 | function HasCollector(const AName: string): Boolean; 59 | /// 60 | /// Registers a collector within this registry. 61 | /// 62 | procedure &Register(ACollector: TCollector); 63 | /// 64 | /// Unregisters a collector from this registry. 65 | /// 66 | procedure Unregister(ACollector: TCollector); 67 | /// 68 | /// Returns the default registry instance. 69 | /// 70 | /// 71 | /// Collectors can be registered in this default instance 72 | /// or you can create a new registry instance by your own. 73 | /// 74 | class property DefaultRegistry: TCollectorRegistry read GetDefaultRegistry; 75 | end; 76 | 77 | implementation 78 | 79 | uses 80 | System.SysUtils, 81 | Prometheus.Resources; 82 | 83 | { TCollectorRegistry } 84 | 85 | constructor TCollectorRegistry.Create(AOwnsCollectors: Boolean); 86 | begin 87 | inherited Create; 88 | if AOwnsCollectors then 89 | FCollectorsToNames := TObjectDictionary>.Create([doOwnsKeys]) 90 | else 91 | FCollectorsToNames := TDictionary>.Create; 92 | FLock := TObject.Create; 93 | FNamesToCollectors := TDictionary.Create; 94 | end; 95 | 96 | destructor TCollectorRegistry.Destroy; 97 | begin 98 | if Assigned(FCollectorsToNames) then 99 | FreeAndNil(FCollectorsToNames); 100 | if Assigned(FLock) then 101 | FreeAndNil(FLock); 102 | if Assigned(FNamesToCollectors) then 103 | FreeAndNil(FNamesToCollectors); 104 | inherited Destroy; 105 | end; 106 | 107 | class destructor TCollectorRegistry.Finalize; 108 | begin 109 | if Assigned(FDefaultRegistry) then 110 | FreeAndNil(FDefaultRegistry); 111 | end; 112 | 113 | procedure TCollectorRegistry.Clear; 114 | begin 115 | TMonitor.Enter(FLock); 116 | try 117 | FCollectorsToNames.Clear; 118 | FNamesToCollectors.Clear; 119 | finally 120 | TMonitor.Exit(FLock); 121 | end; 122 | end; 123 | 124 | function TCollectorRegistry.Collect: TArray; 125 | begin 126 | TMonitor.Enter(FLock); 127 | try 128 | SetLength(Result, 0); 129 | if FCollectorsToNames.Count <= 0 then 130 | Exit; 131 | var LSamples := TList>.Create; 132 | try 133 | for var LCollectorItem in FCollectorsToNames.Keys do 134 | LSamples.Add(LCollectorItem.Collect); 135 | Result := TArray.Concat(LSamples.ToArray); 136 | finally 137 | LSamples.Free; 138 | end; 139 | finally 140 | TMonitor.Exit(FLock); 141 | end; 142 | end; 143 | 144 | function TCollectorRegistry.GetCollector(const AName: string): T; 145 | var 146 | LItem: TCollector; 147 | begin 148 | TMonitor.Enter(FLock); 149 | try 150 | if FNamesToCollectors.TryGetValue(AName, LItem) then 151 | Result := T(LItem) 152 | else 153 | Result := nil; 154 | finally 155 | TMonitor.Exit(FLock); 156 | end; 157 | end; 158 | 159 | class function TCollectorRegistry.GetDefaultRegistry: TCollectorRegistry; 160 | begin 161 | if not Assigned(FDefaultRegistry) then 162 | FDefaultRegistry := TCollectorRegistry.Create; 163 | Result := FDefaultRegistry; 164 | end; 165 | 166 | function TCollectorRegistry.HasCollector(const AName: string): Boolean; 167 | begin 168 | TMonitor.Enter(FLock); 169 | try 170 | Result := FNamesToCollectors.ContainsKey(AName); 171 | finally 172 | TMonitor.Exit(FLock); 173 | end; 174 | end; 175 | 176 | procedure TCollectorRegistry.Register(ACollector: TCollector); 177 | begin 178 | TMonitor.Enter(FLock); 179 | try 180 | if not Assigned(ACollector) then 181 | raise EArgumentException.Create(StrErrNullCollector); 182 | var LCollectorNames := ACollector.GetNames; 183 | for var LNameToCheck in LCollectorNames do 184 | begin 185 | if FNamesToCollectors.ContainsKey(LNameToCheck) then 186 | raise EListError.Create(StrErrCollectorNameInUse); 187 | end; 188 | for var LNameToAdd in LCollectorNames do 189 | FNamesToCollectors.AddOrSetValue(LNameToAdd, ACollector); 190 | FCollectorsToNames.AddOrSetValue(ACollector, LCollectorNames); 191 | finally 192 | TMonitor.Exit(FLock); 193 | end; 194 | end; 195 | 196 | procedure TCollectorRegistry.Unregister(ACollector: TCollector); 197 | begin 198 | TMonitor.Enter(FLock); 199 | try 200 | FCollectorsToNames.Remove(ACollector); 201 | for var LName in ACollector.GetNames do 202 | FNamesToCollectors.Remove(LName); 203 | finally 204 | TMonitor.Exit(FLock); 205 | end; 206 | end; 207 | 208 | end. 209 | -------------------------------------------------------------------------------- /Source/Prometheus.Resources.pas: -------------------------------------------------------------------------------- 1 | unit Prometheus.Resources; 2 | 3 | interface 4 | 5 | { Resource strings } 6 | 7 | resourcestring 8 | StrErrEmptyMetricName = 'Metric name is empty'; 9 | StrErrEmptyLabelName = 'Metric label name is empty'; 10 | StrErrInvalidMetricName = 'Invalid metric name'; 11 | StrErrInvalidLabelName = 'Invalid metric label name'; 12 | StrErrReservedLabelName = 'Metric label name is reserved for internal use'; 13 | StrErrLabelNameValueMismatch = 'Length must match label names'; 14 | StrErrLabelValuesMissing = 'Label values are missing'; 15 | StrErrAmountLessThanZero = 'Amount must be greater than zero'; 16 | StrErrNullProcReference = 'Reference to procedure not assigned'; 17 | StrErrNullCollector = 'Collector reference not assigned'; 18 | StrErrCollectorNameInUse = 'Name is already in use by another registered collector'; 19 | StrErrCollectorHasLabels = 'This collectors has labels: use label values to retrieve children collectors'; 20 | StrErrHistogramOwnerNil = 'Histogram owner is not assigned'; 21 | StrErrHistogramOwnerNoBuckets = 'Histogram owner has no buckets'; 22 | 23 | implementation 24 | 25 | end. 26 | -------------------------------------------------------------------------------- /Source/Prometheus.Samples.pas: -------------------------------------------------------------------------------- 1 | unit Prometheus.Samples; 2 | 3 | interface 4 | 5 | uses 6 | Prometheus.Labels, 7 | Prometheus.Metrics; 8 | 9 | type 10 | 11 | { Enums } 12 | 13 | TMetricType = (mtCounter, mtGauge, mtHistogram); 14 | 15 | { TSample } 16 | 17 | /// 18 | /// Represents a typical sample that can belong to any metrics and can 19 | /// be associated to label names and values. Each sample can be scraped 20 | /// forming the actual time series data stored inside Prometheus. 21 | /// 22 | TSample = record 23 | /// 24 | /// The metric name this sample belongs to. 25 | /// 26 | MetricName: string; 27 | /// 28 | /// The names for the labels this sample is bound to. 29 | /// 30 | LabelNames: TLabelNames; 31 | /// 32 | /// The values for the labels this sample is bound to. 33 | /// 34 | LabelValues: TArray; 35 | /// 36 | /// The current value of this sample. 37 | /// 38 | Value: Double; 39 | /// 40 | /// Check if this sample is bound to label names and values. 41 | /// 42 | function HasLabels: Boolean; 43 | end; 44 | 45 | { PSample } 46 | 47 | /// 48 | /// Represents a pointer to a sample record. 49 | /// 50 | PSample = ^TSample; 51 | 52 | { TMetricSamples } 53 | 54 | /// 55 | /// Represents a set of samples collected for a specific metric. 56 | /// 57 | TMetricSamples = record 58 | /// 59 | /// The name of the metric. 60 | /// 61 | MetricName: string; 62 | /// 63 | /// The help text for the metric. 64 | /// 65 | MetricHelp: string; 66 | /// 67 | /// The type of the metric. 68 | /// 69 | MetricType: TMetricType; 70 | /// 71 | /// The set of samples collected for the metric. 72 | /// 73 | Samples: TArray; 74 | /// 75 | /// The sum off all metrics values (used for histograms). 76 | /// 77 | MetricSum: Double; 78 | /// 79 | /// The count of all observed processes (used for histograms). 80 | /// 81 | MetricCount: Int64; 82 | /// 83 | /// Indicates whether the current metric has no samples in it. 84 | /// 85 | function IsEmpty: Boolean; 86 | end; 87 | 88 | { PMetricSamples } 89 | 90 | /// 91 | /// Represents a pointer to a set of metric samples. 92 | /// 93 | PMetricSamples = ^TMetricSamples; 94 | 95 | const 96 | StrMetricType: array[TMetricType] of string = ( 97 | 'counter', 'gauge', 'histogram' 98 | ); 99 | 100 | implementation 101 | 102 | { TSample } 103 | 104 | function TSample.HasLabels: Boolean; 105 | begin 106 | Result := (Length(LabelNames) > 0) and (Length(LabelValues) > 0); 107 | end; 108 | 109 | { TMetricSamples } 110 | 111 | function TMetricSamples.IsEmpty: Boolean; 112 | begin 113 | Result := Length(Samples) <= 0; 114 | end; 115 | 116 | end. 117 | -------------------------------------------------------------------------------- /Source/Prometheus.SimpleCollector.pas: -------------------------------------------------------------------------------- 1 | unit Prometheus.SimpleCollector; 2 | 3 | interface 4 | 5 | uses 6 | System.Generics.Collections, 7 | Prometheus.Collector, 8 | Prometheus.Labels, 9 | Prometheus.Metrics, 10 | Prometheus.Registry; 11 | 12 | type 13 | 14 | { References to procedures } 15 | 16 | /// 17 | /// Represents a callback procedure that can be passed to a method that 18 | /// enumerates the children of a collector using a specific criteria. 19 | /// 20 | TChildrenCallback = reference to procedure ( 21 | const ALabelValues: TLabelValues; const AChild: TChild 22 | ); 23 | 24 | { TSimpleCollector } 25 | 26 | /// 27 | /// Represents the base class that any typical collector must inherit 28 | /// and provides all the basic features, like registration and so on. 29 | /// 30 | TSimpleCollector = class abstract(TCollector) 31 | strict private 32 | FChildren: TDictionary; 33 | FHelp: string; 34 | FLabelNames: TLabelNames; 35 | FLock: TObject; 36 | FName: string; 37 | procedure InitializeNoLabelChildIfNeeded(); 38 | function GetChildrenCount: Integer; 39 | strict protected 40 | function CreateChild: TChild; virtual; 41 | procedure EnumChildren(ACallback: TChildrenCallback); 42 | function GetNoLabelChild: TChild; 43 | property Lock: TObject read FLock; 44 | public 45 | /// 46 | /// Creates a new instance of this collector. 47 | /// 48 | constructor Create(const AName: string; const AHelp: string = ''; 49 | const ALabelNames: TLabelNames = []); virtual; 50 | /// 51 | /// Performs object cleanup releasing all the owned instances. 52 | /// 53 | destructor Destroy; override; 54 | /// 55 | /// Clears any labelled children owned by this collector. 56 | /// 57 | procedure Clear; 58 | /// 59 | /// Create or retrieve the metric child for the specified label values. 60 | /// 61 | /// 62 | /// The count of label values must match the count of label names. 63 | /// 64 | function Labels(const ALabelValues: TLabelValues): TChild; 65 | /// 66 | /// Remove the metric child for the specified label values. 67 | /// 68 | procedure RemoveLabels(const ALabelValues: TLabelValues); 69 | /// 70 | /// Registers this collector within the specified registry. 71 | /// 72 | procedure &Register(ARegistry: TCollectorRegistry = nil); 73 | /// 74 | /// Unregister this collector from the specified registry. 75 | /// 76 | procedure Unregister(ARegistry: TCollectorRegistry = nil); 77 | /// 78 | /// Returns the count of children for this collector. 79 | /// 80 | property ChildrenCount: Integer read GetChildrenCount; 81 | /// 82 | /// Returns the help text for the metric. 83 | /// 84 | property Help: string read FHelp; 85 | /// 86 | /// Returns the set of label names for the metric. 87 | /// 88 | property LabelNames: TLabelNames read FLabelNames; 89 | /// 90 | /// Returns the name of this collector. 91 | /// 92 | property Name: string read FName; 93 | end; 94 | 95 | implementation 96 | 97 | uses 98 | System.SysUtils, 99 | Prometheus.Resources; 100 | 101 | { TSimpleCollector } 102 | 103 | constructor TSimpleCollector.Create(const AName: string; 104 | const AHelp: string = ''; const ALabelNames: TLabelNames = []); 105 | begin 106 | inherited Create(); 107 | FLock := TObject.Create; 108 | TMetricValidator.CheckName(AName); 109 | if Length(ALabelNames) > 0 then 110 | TLabelValidator.CheckLabels(ALabelNames); 111 | FName := AName; 112 | FHelp := AHelp; 113 | FLabelNames := ALabelNames; 114 | FChildren := TObjectDictionary.Create([doOwnsValues], 115 | TLabelNamesEqualityComparer.Create); 116 | InitializeNoLabelChildIfNeeded; 117 | end; 118 | 119 | destructor TSimpleCollector.Destroy; 120 | begin 121 | if Assigned(FChildren) then 122 | FreeAndNil(FChildren); 123 | if Assigned(FLock) then 124 | FreeAndNil(FLock); 125 | inherited Destroy; 126 | end; 127 | 128 | function TSimpleCollector.CreateChild: TChild; 129 | begin 130 | Result := nil; 131 | end; 132 | 133 | procedure TSimpleCollector.EnumChildren(ACallback: TChildrenCallback); 134 | begin 135 | TMonitor.Enter(FLock); 136 | try 137 | for var LChild in FChildren do 138 | ACallback(LChild.Key, LChild.Value); 139 | finally 140 | TMonitor.Exit(FLock); 141 | end; 142 | end; 143 | 144 | function TSimpleCollector.GetChildrenCount: Integer; 145 | begin 146 | TMonitor.Enter(FLock); 147 | try 148 | Result := FChildren.Count; 149 | finally 150 | TMonitor.Exit(FLock); 151 | end; 152 | end; 153 | 154 | function TSimpleCollector.GetNoLabelChild: TChild; 155 | begin 156 | TMonitor.Enter(FLock); 157 | try 158 | if Length(FLabelNames) > 0 then 159 | raise EInvalidOpException.Create(StrErrCollectorHasLabels); 160 | InitializeNoLabelChildIfNeeded; 161 | Result := FChildren.Values.ToArray[0]; 162 | finally 163 | TMonitor.Exit(FLock); 164 | end; 165 | end; 166 | 167 | procedure TSimpleCollector.InitializeNoLabelChildIfNeeded; 168 | begin 169 | TMonitor.Enter(FLock); 170 | try 171 | if (Length(FLabelNames) <= 0) and (FChildren.Count <= 0) then 172 | FChildren.Add(nil, CreateChild); 173 | finally 174 | TMonitor.Exit(FLock); 175 | end; 176 | end; 177 | 178 | function TSimpleCollector.Labels(const ALabelValues: TLabelValues): TChild; 179 | begin 180 | TMonitor.Enter(FLock); 181 | try 182 | if Length(ALabelValues) <= 0 then 183 | raise EArgumentException.Create(StrErrLabelValuesMissing); 184 | if Length(ALabelValues) <> Length(FLabelNames) then 185 | raise EArgumentException.Create(StrErrLabelNameValueMismatch); 186 | if FChildren.TryGetValue(ALabelValues, Result) then 187 | Exit; 188 | Result := CreateChild; 189 | if not Assigned(Result) then 190 | Exit; 191 | try 192 | FChildren.Add(ALabelValues, Result); 193 | except 194 | FreeAndNil(Result); 195 | raise; 196 | end; 197 | finally 198 | TMonitor.Exit(FLock); 199 | end; 200 | end; 201 | 202 | procedure TSimpleCollector.Clear; 203 | begin 204 | TMonitor.Enter(FLock); 205 | try 206 | FChildren.Clear; 207 | InitializeNoLabelChildIfNeeded; 208 | finally 209 | TMonitor.Exit(FLock); 210 | end; 211 | end; 212 | 213 | procedure TSimpleCollector.Register(ARegistry: TCollectorRegistry); 214 | begin 215 | if not Assigned(ARegistry) then 216 | ARegistry := TCollectorRegistry.DefaultRegistry; 217 | ARegistry.&Register(Self); 218 | end; 219 | 220 | procedure TSimpleCollector.RemoveLabels(const ALabelValues: TLabelValues); 221 | begin 222 | TMonitor.Enter(FLock); 223 | try 224 | FChildren.Remove(ALabelValues); 225 | InitializeNoLabelChildIfNeeded; 226 | finally 227 | TMonitor.Exit(FLock); 228 | end; 229 | end; 230 | 231 | procedure TSimpleCollector.Unregister(ARegistry: TCollectorRegistry); 232 | begin 233 | if not Assigned(ARegistry) then 234 | ARegistry := TCollectorRegistry.DefaultRegistry; 235 | ARegistry.Unregister(Self); 236 | end; 237 | 238 | end. 239 | -------------------------------------------------------------------------------- /Tests/Fixtures/Prometheus.Tests.Fixtures.Collector.pas: -------------------------------------------------------------------------------- 1 | unit Prometheus.Tests.Fixtures.Collector; 2 | 3 | interface 4 | 5 | uses 6 | DUnitX.TestFramework; 7 | 8 | type 9 | 10 | { TCollectorTestFixture } 11 | 12 | [TestFixture] 13 | TCollectorTestFixture = class 14 | strict private 15 | public 16 | [Setup] 17 | procedure Setup; 18 | [TearDown] 19 | procedure TearDown; 20 | [Test] 21 | procedure CollectorRegisterShouldUseDefaultRegistry; 22 | [Test] 23 | procedure CollectorMustSupportMoreRegistries; 24 | end; 25 | 26 | implementation 27 | 28 | uses 29 | Prometheus.Collector, 30 | Prometheus.Collectors.Counter, 31 | Prometheus.Registry; 32 | 33 | { TCollectorTestFixture } 34 | 35 | procedure TCollectorTestFixture.Setup; 36 | begin 37 | end; 38 | 39 | procedure TCollectorTestFixture.TearDown; 40 | begin 41 | end; 42 | 43 | procedure TCollectorTestFixture.CollectorMustSupportMoreRegistries; 44 | begin 45 | var LRegistry1 := TCollectorRegistry.Create(True); 46 | var LRegistry2 := TCollectorRegistry.Create(False); 47 | var LCollector := TCounter.Create('sample', 'sample collector'); 48 | LCollector.Register(LRegistry1); 49 | LCollector.Register(LRegistry2); 50 | Assert.IsTrue((LCollector = LRegistry1.GetCollector('sample')) 51 | and (LCollector = LRegistry2.GetCollector('sample'))); 52 | LRegistry1.Free; 53 | LRegistry2.Free; 54 | end; 55 | 56 | procedure TCollectorTestFixture.CollectorRegisterShouldUseDefaultRegistry; 57 | begin 58 | var LRegistry := TCollectorRegistry.DefaultRegistry; 59 | var LCollector := TCounter.Create('sample'); 60 | LCollector.Register(); 61 | Assert.AreEqual(LCollector, LRegistry.GetCollector('sample')); 62 | end; 63 | 64 | initialization 65 | 66 | TDUnitX.RegisterTestFixture(TCollectorTestFixture); 67 | 68 | end. 69 | -------------------------------------------------------------------------------- /Tests/Fixtures/Prometheus.Tests.Fixtures.Collectors.Counter.pas: -------------------------------------------------------------------------------- 1 | unit Prometheus.Tests.Fixtures.Collectors.Counter; 2 | 3 | interface 4 | 5 | uses 6 | DUnitX.TestFramework; 7 | 8 | type 9 | 10 | { TCounterCollectorTestFixture } 11 | 12 | [TestFixture] 13 | TCounterCollectorTestFixture = class 14 | public 15 | [Test] 16 | procedure CounterMustStartAtZero; 17 | [Test] 18 | procedure CounterMustIncrementByOneAsDefault; 19 | [Test] 20 | procedure CounterMustIncrementBySpecifiedAmount; 21 | [Test] 22 | procedure CounterMustThrowExceptionIfAmountIsNegative; 23 | end; 24 | 25 | implementation 26 | 27 | uses 28 | System.SysUtils, 29 | Prometheus.Collectors.Counter; 30 | 31 | { TCounterCollectorTestFixture } 32 | 33 | procedure TCounterCollectorTestFixture.CounterMustIncrementByOneAsDefault; 34 | begin 35 | var LCounter := TCounter.Create('sample', 'sample counter'); 36 | try 37 | LCounter.Inc(); 38 | Assert.AreEqual(1, LCounter.Value, Double.Epsilon); 39 | finally 40 | LCounter.Free; 41 | end; 42 | end; 43 | 44 | procedure TCounterCollectorTestFixture.CounterMustIncrementBySpecifiedAmount; 45 | begin 46 | var LCounter := TCounter.Create('sample', 'sample counter'); 47 | try 48 | LCounter.Inc(123); 49 | Assert.AreEqual(123, LCounter.Value, Double.Epsilon); 50 | finally 51 | LCounter.Free; 52 | end; 53 | end; 54 | 55 | procedure TCounterCollectorTestFixture.CounterMustStartAtZero; 56 | begin 57 | var LCounter := TCounter.Create('sample', 'sample counter'); 58 | try 59 | Assert.AreEqual(0, LCounter.Value, Double.Epsilon); 60 | finally 61 | LCounter.Free; 62 | end; 63 | end; 64 | 65 | procedure TCounterCollectorTestFixture.CounterMustThrowExceptionIfAmountIsNegative; 66 | begin 67 | var LCounter := TCounter.Create('sample', 'sample counter'); 68 | try 69 | Assert.WillRaise( 70 | procedure 71 | begin 72 | LCounter.Inc(-1); 73 | end, EArgumentOutOfRangeException); 74 | finally 75 | LCounter.Free; 76 | end; 77 | end; 78 | 79 | initialization 80 | 81 | TDUnitX.RegisterTestFixture(TCounterCollectorTestFixture); 82 | 83 | end. 84 | -------------------------------------------------------------------------------- /Tests/Fixtures/Prometheus.Tests.Fixtures.Collectors.Gauge.pas: -------------------------------------------------------------------------------- 1 | unit Prometheus.Tests.Fixtures.Collectors.Gauge; 2 | 3 | interface 4 | 5 | uses 6 | DUnitX.TestFramework; 7 | 8 | type 9 | 10 | { TGaugeCollectorTestFixture } 11 | 12 | [TestFixture] 13 | TGaugeCollectorTestFixture = class 14 | public 15 | [Test] 16 | procedure GaugeMustStartAtZero; 17 | [Test] 18 | procedure GaugeMustDecrementByOneAsDefault; 19 | [Test] 20 | procedure GaugeMustDecrementBySpecifiedAmount; 21 | [Test] 22 | procedure GaugeMustIncrementByOneAsDefault; 23 | [Test] 24 | procedure GaugeMustIncrementBySpecifiedAmount; 25 | [Test] 26 | procedure GaugeMustSetCurrentValue; 27 | end; 28 | 29 | implementation 30 | 31 | uses 32 | System.SysUtils, 33 | Prometheus.Collectors.Gauge; 34 | 35 | { TGaugeCollectorTestFixture } 36 | 37 | procedure TGaugeCollectorTestFixture.GaugeMustStartAtZero; 38 | begin 39 | var LGauge := TGauge.Create('sample', 'sample gauge'); 40 | try 41 | Assert.AreEqual(0, LGauge.Value, Double.Epsilon); 42 | finally 43 | LGauge.Free; 44 | end; 45 | end; 46 | 47 | procedure TGaugeCollectorTestFixture.GaugeMustDecrementByOneAsDefault; 48 | begin 49 | var LGauge := TGauge.Create('sample', 'sample gauge'); 50 | try 51 | LGauge.Dec(); 52 | Assert.AreEqual(-1, LGauge.Value, Double.Epsilon); 53 | finally 54 | LGauge.Free; 55 | end; 56 | end; 57 | 58 | procedure TGaugeCollectorTestFixture.GaugeMustDecrementBySpecifiedAmount; 59 | begin 60 | var LGauge := TGauge.Create('sample', 'sample gauge'); 61 | try 62 | LGauge.Dec(123); 63 | Assert.AreEqual(-123, LGauge.Value, Double.Epsilon); 64 | finally 65 | LGauge.Free; 66 | end; 67 | end; 68 | 69 | procedure TGaugeCollectorTestFixture.GaugeMustIncrementByOneAsDefault; 70 | begin 71 | var LGauge := TGauge.Create('sample', 'sample gauge'); 72 | try 73 | LGauge.Inc(); 74 | Assert.AreEqual(1, LGauge.Value, Double.Epsilon); 75 | finally 76 | LGauge.Free; 77 | end; 78 | end; 79 | 80 | procedure TGaugeCollectorTestFixture.GaugeMustIncrementBySpecifiedAmount; 81 | begin 82 | var LGauge := TGauge.Create('sample', 'sample gauge'); 83 | try 84 | LGauge.Inc(123); 85 | Assert.AreEqual(123, LGauge.Value, Double.Epsilon); 86 | finally 87 | LGauge.Free; 88 | end; 89 | end; 90 | 91 | procedure TGaugeCollectorTestFixture.GaugeMustSetCurrentValue; 92 | begin 93 | var LGauge := TGauge.Create('sample', 'sample gauge'); 94 | try 95 | LGauge.SetTo(123); 96 | Assert.AreEqual(123, LGauge.Value, Double.Epsilon); 97 | finally 98 | LGauge.Free; 99 | end; 100 | end; 101 | 102 | initialization 103 | 104 | TDUnitX.RegisterTestFixture(TGaugeCollectorTestFixture); 105 | 106 | end. 107 | -------------------------------------------------------------------------------- /Tests/Fixtures/Prometheus.Tests.Fixtures.Collectors.Histogram.pas: -------------------------------------------------------------------------------- 1 | unit Prometheus.Tests.Fixtures.Collectors.Histogram; 2 | 3 | interface 4 | 5 | uses 6 | DUnitX.TestFramework; 7 | 8 | type 9 | 10 | { THistogramCollectorTestFixture } 11 | 12 | [TestFixture] 13 | THistogramCollectorTestFixture = class 14 | public 15 | [Test] 16 | procedure HistogramBucketsMustBeSorted; 17 | [Test] 18 | procedure HistogramBucketsMustIncrementBySpecifiedAmount; 19 | [Test] 20 | procedure HistogramBucketsMustStartAtZero; 21 | [Test] 22 | procedure HistogramCountMustIncrementBySpecifiedAmount; 23 | [Test] 24 | procedure HistogramCountMustStartAtZero; 25 | [Test] 26 | procedure HistogramLabelMustThrowExceptionIfUseReservedName; 27 | [Test] 28 | procedure HistogramSumMustIncrementBySpecifiedAmount; 29 | [Test] 30 | procedure HistogramSumMustStartAtZero; 31 | end; 32 | 33 | implementation 34 | 35 | uses 36 | System.SysUtils, 37 | Prometheus.Collectors.Histogram; 38 | 39 | { THistogramCollectorTestFixture } 40 | 41 | procedure THistogramCollectorTestFixture.HistogramBucketsMustBeSorted; 42 | begin 43 | var LHistogram := THistogram.Create('Sample', '', [2, 0.5, 1]); 44 | try 45 | Assert.AreEqual(4, Length(LHistogram.Buckets)); 46 | Assert.AreEqual(0.5, LHistogram.Buckets[0], Double.Epsilon); 47 | Assert.AreEqual(1.0, LHistogram.Buckets[1], Double.Epsilon); 48 | Assert.AreEqual(2.0, LHistogram.Buckets[2], Double.Epsilon); 49 | Assert.AreEqual(INFINITE, LHistogram.Buckets[3], Double.Epsilon); 50 | finally 51 | LHistogram.Free; 52 | end; 53 | end; 54 | 55 | procedure THistogramCollectorTestFixture.HistogramBucketsMustIncrementBySpecifiedAmount; 56 | begin 57 | var LHistogram := THistogram.Create('Sample', '', [0.025, 0.05]); 58 | try 59 | LHistogram.Observe(0.01); 60 | LHistogram.Observe(0.04); 61 | LHistogram.Observe(0.05); 62 | LHistogram.Observe(1); 63 | 64 | var LMetricArray := LHistogram.Collect; 65 | Assert.AreEqual(1, Length( LMetricArray)); 66 | 67 | for var LMetric in LMetricArray do 68 | begin 69 | Assert.AreEqual('Sample', LMetric.MetricName); 70 | Assert.AreEqual(4, LMetric.MetricCount, 0); 71 | 72 | for var LSample in LMetric.Samples do 73 | begin 74 | Assert.AreEqual('Sample_bucket', LSample.MetricName); 75 | Assert.AreEqual('le', LSample.LabelNames[0]); 76 | if LSample.LabelValues[0] = '0.025' then 77 | Assert.AreEqual(1, LSample.Value, 0) 78 | else if LSample.LabelValues[0] = '0.05' then 79 | Assert.AreEqual(3, LSample.Value, 0) 80 | else if LSample.LabelValues[0] = '+Inf' then 81 | Assert.AreEqual(4, LSample.Value, 0); 82 | end; 83 | end; 84 | finally 85 | LHistogram.Free; 86 | end; 87 | end; 88 | 89 | procedure THistogramCollectorTestFixture.HistogramBucketsMustStartAtZero; 90 | begin 91 | var LHistogram := THistogram.Create('Sample', '', [0.025, 0.05]); 92 | try 93 | var LMetricArray := LHistogram.Collect; 94 | 95 | Assert.AreEqual(1, Length( LMetricArray)); 96 | for var LMetric in LMetricArray do 97 | begin 98 | Assert.AreEqual('Sample', LMetric.MetricName); 99 | Assert.AreEqual(0, LMetric.MetricCount, 0); 100 | for var LSample in LMetric.Samples do 101 | begin 102 | Assert.AreEqual('Sample_bucket', LSample.MetricName); 103 | Assert.AreEqual('le', LSample.LabelNames[0]); 104 | if LSample.LabelValues[0] = '0.025' then 105 | Assert.AreEqual(0, LSample.Value, 0); 106 | if LSample.LabelValues[0] = '0.05' then 107 | Assert.AreEqual(0, LSample.Value, 0); 108 | if LSample.LabelValues[0] = '+Inf' then 109 | Assert.AreEqual(0, LSample.Value, 0); 110 | end; 111 | end; 112 | finally 113 | LHistogram.Free; 114 | end; 115 | end; 116 | 117 | procedure THistogramCollectorTestFixture.HistogramCountMustIncrementBySpecifiedAmount; 118 | begin 119 | var LHistogram := THistogram.Create('Sample', '', [0.025, 0.05]); 120 | try 121 | LHistogram.Observe(0.01); 122 | LHistogram.Observe(0.04); 123 | LHistogram.Observe(1); 124 | Assert.AreEqual(3, LHistogram.Count, 0); 125 | finally 126 | LHistogram.Free; 127 | end; 128 | end; 129 | 130 | procedure THistogramCollectorTestFixture.HistogramCountMustStartAtZero; 131 | begin 132 | var LHistogram := THistogram.Create('Sample'); 133 | try 134 | Assert.AreEqual(0, LHistogram.Count, Double.Epsilon); 135 | finally 136 | LHistogram.Free; 137 | end; 138 | end; 139 | 140 | procedure THistogramCollectorTestFixture.HistogramLabelMustThrowExceptionIfUseReservedName; 141 | begin 142 | Assert.WillRaise( 143 | procedure 144 | begin 145 | THistogram.Create('Sample', '', [], ['le']); 146 | end, 147 | EInvalidOpException); 148 | end; 149 | 150 | procedure THistogramCollectorTestFixture.HistogramSumMustIncrementBySpecifiedAmount; 151 | begin 152 | var LHistogram := THistogram.Create('Sample', '', [0.025, 0.05]); 153 | try 154 | LHistogram.Observe(0.01); 155 | LHistogram.Observe(0.04); 156 | LHistogram.Observe(1); 157 | Assert.AreEqual(1.05, LHistogram.Sum ,0); 158 | finally 159 | LHistogram.Free; 160 | end; 161 | end; 162 | 163 | procedure THistogramCollectorTestFixture.HistogramSumMustStartAtZero; 164 | begin 165 | var LHistogram := THistogram.Create('Sample'); 166 | try 167 | Assert.AreEqual(0, LHistogram.Sum, Double.Epsilon); 168 | finally 169 | LHistogram.Free; 170 | end; 171 | end; 172 | 173 | initialization 174 | 175 | TDUnitX.RegisterTestFixture(THistogramCollectorTestFixture); 176 | 177 | end. 178 | -------------------------------------------------------------------------------- /Tests/Prometheus.Client.Tests.dpr: -------------------------------------------------------------------------------- 1 | program Prometheus.Client.Tests; 2 | 3 | {$IFNDEF TESTINSIGHT} 4 | {$APPTYPE CONSOLE} 5 | {$ENDIF} 6 | 7 | {$STRONGLINKTYPES ON} 8 | 9 | uses 10 | System.SysUtils, 11 | {$IFNDEF TESTINSIGHT} 12 | DUnitX.Loggers.Console, 13 | DUnitX.Loggers.Xml.NUnit, 14 | {$ELSE} 15 | TestInsight.DUnitX, 16 | {$ENDIF } 17 | DUnitX.TestFramework, 18 | Prometheus.Tests.Fixtures.Collector in 'Fixtures\Prometheus.Tests.Fixtures.Collector.pas', 19 | Prometheus.Tests.Fixtures.Collectors.Counter in 'Fixtures\Prometheus.Tests.Fixtures.Collectors.Counter.pas', 20 | Prometheus.Tests.Fixtures.Collectors.Gauge in 'Fixtures\Prometheus.Tests.Fixtures.Collectors.Gauge.pas', 21 | Prometheus.Tests.Fixtures.Collectors.Histogram in 'Fixtures\Prometheus.Tests.Fixtures.Collectors.Histogram.pas'; 22 | 23 | begin 24 | {$IFNDEF TESTINSIGHT} 25 | try 26 | ReportMemoryLeaksOnShutdown := True; 27 | 28 | // Check command line options, will exit if invalid. 29 | TDUnitX.CheckCommandLine; 30 | 31 | // Create the test runner. 32 | var LRunner: ITestRunner := TDUnitX.CreateRunner; 33 | 34 | // Tell the runner to use RTTI to find fixtures. 35 | LRunner.UseRTTI := True; 36 | 37 | // When true, Assertions must be made during tests. 38 | LRunner.FailsOnNoAsserts := False; 39 | 40 | // Tell the runner how we will log things. 41 | if TDUnitX.Options.ConsoleMode <> TDunitXConsoleMode.Off then 42 | begin 43 | var LLogger := TDUnitXConsoleLogger.Create( 44 | TDUnitX.Options.ConsoleMode = TDunitXConsoleMode.Quiet); 45 | LRunner.AddLogger(LLogger); 46 | end; 47 | 48 | // Generate an NUnit compatible XML File. 49 | var LXmlLogger := TDUnitXXMLNUnitFileLogger.Create(TDUnitX.Options.XMLOutputFile); 50 | LRunner.AddLogger(LXmlLogger); 51 | 52 | // Run tests and collect results. 53 | var LResults := LRunner.Execute; 54 | if not LResults.AllPassed then 55 | System.ExitCode := EXIT_ERRORS; 56 | except 57 | on E: Exception do 58 | System.Writeln(E.ClassName, ': ', E.Message); 59 | end; 60 | {$IFNDEF CI} 61 | // We don't want this happening when running under CI. 62 | Writeln; 63 | Write('Done.. press key to quit.'); 64 | Readln; 65 | {$ENDIF} 66 | {$ELSE} 67 | TestInsight.DUnitX.RunRegisteredTests; 68 | {$ENDIF} 69 | end. 70 | -------------------------------------------------------------------------------- /Tests/Prometheus.Client.Tests.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcobreveglieri/prometheus-client-delphi/f3bb03adc4f9cf92ef7978f296943e40437a05b7/Tests/Prometheus.Client.Tests.res -------------------------------------------------------------------------------- /boss-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "d41d8cd98f00b204e9800998ecf8427e", 3 | "updated": "2023-03-23T15:46:06.176497+01:00", 4 | "installedModules": {} 5 | } -------------------------------------------------------------------------------- /boss.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prometheus-client-delphi", 3 | "description": "Prometheus Client library for Embarcadero Delphi", 4 | "version": "0.9.1", 5 | "homepage": "https://www.breveglieri.it/progetti/prometheus-delphi-client", 6 | "mainsrc": "./Source", 7 | "projects": [], 8 | "dependencies": {} 9 | } --------------------------------------------------------------------------------