-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implements Counter, Gauge and Histogram. Includes a shelf handler to export metrics and a shelf middleware to measure performance.
- Loading branch information
Showing
28 changed files
with
1,903 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
name: Dart CI | ||
|
||
on: [push, pull_request] | ||
|
||
jobs: | ||
build: | ||
|
||
runs-on: ubuntu-latest | ||
|
||
container: | ||
image: google/dart:latest | ||
|
||
steps: | ||
- uses: actions/checkout@v1 | ||
- name: Install dependencies | ||
run: pub get | ||
- name: Run tests | ||
run: pub run test | ||
- name: Dart/Flutter Package Analyzer | ||
uses: axel-op/[email protected] | ||
with: | ||
githubToken: ${{ secrets.GITHUB_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,11 @@ | ||
# See https://www.dartlang.org/guides/libraries/private-files | ||
|
||
# Files and directories created by pub | ||
.dart_tool/ | ||
.packages | ||
.pub/ | ||
build/ | ||
# If you're building an application, you may want to check-in your pubspec.lock | ||
# Remove the following pattern if you wish to check in your lock file | ||
pubspec.lock | ||
|
||
# Conventional directory for build outputs | ||
build/ | ||
|
||
# Directory created by dartdoc | ||
# If you don't generate documentation locally you can remove this line. | ||
doc/api/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
## 0.1.0 | ||
|
||
- Initial version | ||
- Implements `Counter`, `Gauge` and `Histogram`. | ||
- Includes a shelf handler to export metrics and a shelf middleware to measure performance. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,73 @@ | ||
# prometheus_client | ||
A Dart prometheus client library | ||
prometheus_client | ||
=== | ||
|
||
This is a simple Dart implementation of the [Prometheus][prometheus] client library, [similar to to libraries for other languages][writing_clientlibs]. | ||
It supports the default metric types like gauges, counters, or histograms. | ||
Metrics can be exported using the [text format][text_format]. | ||
To expose them in your server application the package comes with a [shelf][shelf] handler. | ||
In addition, it comes with some plug-in ready metrics for the Dart runtime and shelf. | ||
|
||
You can find the latest updates in the [changelog][changelog]. | ||
|
||
## Usage | ||
|
||
A simple usage example: | ||
|
||
```dart | ||
import 'package:prometheus_client/prometheus_client.dart'; | ||
import 'package:prometheus_client/runtime_metrics.dart' as runtime_metrics; | ||
import 'package:prometheus_client/shelf_handler.dart'; | ||
import 'package:shelf/shelf.dart' as shelf; | ||
import 'package:shelf/shelf_io.dart' as io; | ||
import 'package:shelf_router/shelf_router.dart'; | ||
main() async { | ||
// Register default runtime metrics | ||
runtime_metrics.register(); | ||
// Create a metric of type counter. | ||
// Always register your metric, either at the default registry or a custom one. | ||
final greetingCounter = | ||
Counter('greetings_total', 'The total amount of greetings')..register(); | ||
final app = Router(); | ||
app.get('/hello', (shelf.Request request) { | ||
// Every time the hello is called, increase the counter by one | ||
greetingCounter.inc(); | ||
return shelf.Response.ok('hello-world'); | ||
}); | ||
// Register a handler to expose the metrics in the Prometheus text format | ||
app.get('/metrics', prometheusHandler()); | ||
var handler = const shelf.Pipeline() | ||
.addHandler(app.handler); | ||
var server = await io.serve(handler, 'localhost', 8080); | ||
print('Serving at http://${server.address.host}:${server.port}'); | ||
} | ||
``` | ||
|
||
Start the example application and access the exposed metrics at `http://localhost:8080/metrics`. | ||
For a full usage example, take a look at [`example/prometheus_client.example.dart`][example]. | ||
|
||
## Planned features | ||
|
||
To achieve the requirements from the Prometheus [Writing Client Libraries][writing_clientlibs] documentation, some features still have to be implemented: | ||
|
||
* Support `Summary` metric type. | ||
* Support timestamp in samples and text format. | ||
* Split out shelf support into own package to avoid dependencies on shelf. | ||
|
||
|
||
## Features and bugs | ||
|
||
Please file feature requests and bugs at the [issue tracker][tracker]. | ||
|
||
[tracker]: https://github.com/Fox32/prometheus_client/issues | ||
[writing_clientlibs]: https://prometheus.io/docs/instrumenting/writing_clientlibs/ | ||
[prometheus]: https://prometheus.io/ | ||
[text_format]: https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format | ||
[shelf]: https://pub.dev/packages/shelf | ||
[example]: ./example/prometheus_client_example.dart | ||
[changelog]: ./CHANGELOG.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Defines a default set of lint rules enforced for | ||
# projects at Google. For details and rationale, | ||
# see https://github.com/dart-lang/pedantic#enabled-lints. | ||
include: package:pedantic/analysis_options.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import 'dart:math'; | ||
|
||
import 'package:prometheus_client/prometheus_client.dart'; | ||
import 'package:prometheus_client/runtime_metrics.dart' as runtime_metrics; | ||
import 'package:prometheus_client/shelf_metrics.dart' as shelf_metrics; | ||
import 'package:prometheus_client/shelf_handler.dart'; | ||
import 'package:shelf/shelf.dart' as shelf; | ||
import 'package:shelf/shelf_io.dart' as io; | ||
import 'package:shelf_router/shelf_router.dart'; | ||
|
||
main() async { | ||
runtime_metrics.register(); | ||
|
||
// Create a labeled gauge metric that stores the last time an endpoint was | ||
// accessed. Always register your metric, either at the default registry or a | ||
// custom one. | ||
final timeGauge = Gauge( | ||
'last_accessed_time', 'The last time the hello endpoint was accessed', | ||
labelNames: ['endpoint']) | ||
..register(); | ||
// Create a gauge metric without labels to store the last rolled value | ||
final rollGauge = Gauge('roll_value', 'The last roll value')..register(); | ||
// Create a metric of type counter | ||
final greetingCounter = | ||
Counter('greetings_total', 'The total amount of greetings')..register(); | ||
|
||
final app = Router(); | ||
|
||
app.get('/hello', (shelf.Request request) { | ||
// Set the current time to the time metric for the label 'hello' | ||
timeGauge..labels(['hello']).setToCurrentTime(); | ||
// Every time the hello is called, increase the counter by one | ||
greetingCounter.inc(); | ||
return shelf.Response.ok('hello-world'); | ||
}); | ||
|
||
app.get('/roll', (shelf.Request request) { | ||
timeGauge..labels(['roll']).setToCurrentTime(); | ||
final value = Random().nextDouble(); | ||
// Store the rolled value without labels | ||
rollGauge.value = value; | ||
return shelf.Response.ok('rolled $value'); | ||
}); | ||
|
||
// Register a handler to expose the metrics in the Prometheus text format | ||
app.get('/metrics', prometheusHandler()); | ||
|
||
app.all('/<ignored|.*>', (shelf.Request request) { | ||
return shelf.Response.notFound('Not Found'); | ||
}); | ||
|
||
var handler = const shelf.Pipeline() | ||
// Register a middleware to track request times | ||
.addMiddleware(shelf_metrics.register()) | ||
.addMiddleware(shelf.logRequests()) | ||
.addHandler(app.handler); | ||
var server = await io.serve(handler, 'localhost', 8080); | ||
|
||
print('Serving at http://${server.address.host}:${server.port}'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
/// A library to export metrics in the Prometheus text representation. | ||
library format; | ||
|
||
import 'package:prometheus_client/prometheus_client.dart'; | ||
import 'package:prometheus_client/src/double_format.dart'; | ||
|
||
/// Content-type for text version 0.0.4. | ||
const contentType = 'text/plain; version=0.0.4; charset=utf-8'; | ||
|
||
/// Write out the text version 0.0.4 of the given [MetricFamilySamples]. | ||
void write004( | ||
StringSink sink, Iterable<MetricFamilySamples> metricFamilySamples) { | ||
// See http://prometheus.io/docs/instrumenting/exposition_formats/ | ||
// for the output format specification | ||
for (var metricFamilySample in metricFamilySamples) { | ||
sink.write('# HELP '); | ||
sink.write(metricFamilySample.name); | ||
sink.write(' '); | ||
_writeEscapedHelp(sink, metricFamilySample.help); | ||
sink.write('\n'); | ||
|
||
sink.write('# TYPE '); | ||
sink.write(metricFamilySample.name); | ||
sink.write(' '); | ||
_writeMetricType(sink, metricFamilySample.type); | ||
sink.write('\n'); | ||
|
||
for (var sample in metricFamilySample.samples) { | ||
sink.write(sample.name); | ||
if (sample.labelNames.isNotEmpty) { | ||
sink.write('{'); | ||
for (var i = 0; i < sample.labelNames.length; ++i) { | ||
sink.write(sample.labelNames[i]); | ||
sink.write('="'); | ||
_writeEscapedLabelValue(sink, sample.labelValues[i]); | ||
sink.write('\",'); | ||
} | ||
sink.write('}'); | ||
} | ||
sink.write(' '); | ||
sink.write(formatDouble(sample.value)); | ||
// TODO: Write Timestamp | ||
sink.writeln(); | ||
} | ||
} | ||
} | ||
|
||
void _writeMetricType(StringSink sink, MetricType type) { | ||
switch (type) { | ||
case MetricType.counter: | ||
sink.write('counter'); | ||
break; | ||
case MetricType.gauge: | ||
sink.write('gauge'); | ||
break; | ||
case MetricType.summary: | ||
sink.write('summary'); | ||
break; | ||
case MetricType.histogram: | ||
sink.write('histogram'); | ||
break; | ||
case MetricType.untyped: | ||
sink.write('untyped'); | ||
break; | ||
} | ||
} | ||
|
||
const _codeUnitLineFeed = 10; // \n | ||
const _codeUnitBackslash = 92; // \ | ||
const _codeUnitDoubleQuotes = 34; // " | ||
|
||
void _writeEscapedHelp(StringSink sink, String help) { | ||
for (var i = 0; i < help.length; ++i) { | ||
var c = help.codeUnitAt(i); | ||
switch (c) { | ||
case _codeUnitBackslash: | ||
sink.write('\\\\'); | ||
break; | ||
case _codeUnitLineFeed: | ||
sink.write('\\n'); | ||
break; | ||
default: | ||
sink.writeCharCode(c); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
void _writeEscapedLabelValue(StringSink sink, String labelValue) { | ||
for (var i = 0; i < labelValue.length; ++i) { | ||
var c = labelValue.codeUnitAt(i); | ||
switch (c) { | ||
case _codeUnitBackslash: | ||
sink.write('\\\\'); | ||
break; | ||
case _codeUnitDoubleQuotes: | ||
sink.write('\\"'); | ||
break; | ||
case _codeUnitLineFeed: | ||
sink.write('\\n'); | ||
break; | ||
default: | ||
sink.writeCharCode(c); | ||
break; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/// A library containing the core elements of the Prometheus client, like the | ||
/// [CollectorRegistry] and different types of metrics like [Counter], [Gauge] | ||
/// and [Histogram]. | ||
library prometheus_client; | ||
|
||
import 'dart:async'; | ||
import 'dart:collection'; | ||
import 'dart:math' as math; | ||
|
||
import "package:collection/collection.dart"; | ||
import 'package:prometheus_client/src/double_format.dart'; | ||
|
||
part 'src/prometheus_client/collector.dart'; | ||
|
||
part 'src/prometheus_client/counter.dart'; | ||
|
||
part 'src/prometheus_client/gauge.dart'; | ||
|
||
part 'src/prometheus_client/helper.dart'; | ||
|
||
part 'src/prometheus_client/histogram.dart'; | ||
|
||
part 'src/prometheus_client/simple_collector.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/// A library exposing metrics of the Dart runtime. | ||
library runtime_metrics; | ||
|
||
import 'dart:io'; | ||
import 'package:prometheus_client/prometheus_client.dart'; | ||
|
||
// This is not the actual startup time of the process, but the time the first | ||
// collector was created. Dart's lazy initialization of globals doesn't allow | ||
// for a better timing... | ||
|
||
/// Collector for runtime metrics. Exposes the `dart_info` and | ||
/// `process_resident_memory_bytes` metric. | ||
class RuntimeCollector extends Collector { | ||
static final _startupTime = | ||
DateTime.now().millisecondsSinceEpoch / Duration.millisecondsPerSecond; | ||
|
||
@override | ||
Iterable<MetricFamilySamples> collect() sync* { | ||
yield MetricFamilySamples("dart_info", MetricType.gauge, | ||
"Information about the Dart environment.", [ | ||
Sample("dart_info", const ["version"], [Platform.version], 1) | ||
]); | ||
|
||
yield MetricFamilySamples("process_resident_memory_bytes", MetricType.gauge, | ||
"Resident memory size in bytes.", [ | ||
Sample("process_resident_memory_bytes", const [], const [], | ||
ProcessInfo.currentRss.toDouble()) | ||
]); | ||
|
||
yield MetricFamilySamples("process_start_time_seconds", MetricType.gauge, | ||
"Start time of the process since unix epoch in seconds.", [ | ||
Sample("process_start_time_seconds", const [], const [], | ||
_startupTime.toDouble()) | ||
]); | ||
} | ||
} | ||
|
||
/// Register default metrics for the Dart runtime. If no [registry] is provided, | ||
/// the [CollectorRegistry.defaultRegistry] is used. | ||
void register([CollectorRegistry registry]) { | ||
registry ??= CollectorRegistry.defaultRegistry; | ||
|
||
registry.register(RuntimeCollector()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/// A library containing a shelf handler that exposes metrics in the Prometheus | ||
/// text format. | ||
library shelf_handler; | ||
|
||
import 'package:shelf/shelf.dart' as shelf; | ||
import 'package:prometheus_client/prometheus_client.dart'; | ||
import 'package:prometheus_client/format.dart' as format; | ||
|
||
/// Create a shelf handler that returns the metrics in the prometheus text | ||
/// representation. If no [registry] is provided, the | ||
/// [CollectorRegistry.defaultRegistry] is used. | ||
prometheusHandler([CollectorRegistry registry]) { | ||
registry ??= CollectorRegistry.defaultRegistry; | ||
|
||
return (shelf.Request request) { | ||
// TODO: Instead of using a StringBuffer we could directly stream to network | ||
final buffer = StringBuffer(); | ||
format.write004(buffer, registry.collectMetricFamilySamples()); | ||
return shelf.Response.ok(buffer.toString(), | ||
headers: {"Content-Type": format.contentType}); | ||
}; | ||
} |
Oops, something went wrong.