forked from vieter-v/vieter
Merge pull request 'Add Prometheus metrics endpoint' (#325) from Chewing_Bever/vieter:metrics into dev
Reviewed-on: vieter-v/vieter#325renovate/busybox-1.x
commit
849bf54979
|
@ -8,15 +8,21 @@ branches:
|
||||||
depends_on:
|
depends_on:
|
||||||
- build
|
- build
|
||||||
|
|
||||||
skip_clone: true
|
|
||||||
|
|
||||||
pipeline:
|
pipeline:
|
||||||
generate:
|
install-modules:
|
||||||
image: *vlang_image
|
image: *vlang_image
|
||||||
pull: true
|
pull: true
|
||||||
commands:
|
commands:
|
||||||
- curl -o vieter -L "https://s3.rustybever.be/vieter/commits/$CI_COMMIT_SHA/vieter-linux-amd64"
|
- export VMODULES=$PWD/.vmodules
|
||||||
- chmod +x vieter
|
- 'cd src && v install'
|
||||||
|
|
||||||
|
generate:
|
||||||
|
image: *vlang_image
|
||||||
|
commands:
|
||||||
|
# - curl -o vieter -L "https://s3.rustybever.be/vieter/commits/$CI_COMMIT_SHA/vieter-linux-amd64"
|
||||||
|
# - chmod +x vieter
|
||||||
|
- export VMODULES=$PWD/.vmodules
|
||||||
|
- make
|
||||||
- ./vieter man man
|
- ./vieter man man
|
||||||
- cd man
|
- cd man
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased](https://git.rustybever.be/vieter-v/vieter/src/branch/dev)
|
## [Unreleased](https://git.rustybever.be/vieter-v/vieter/src/branch/dev)
|
||||||
|
|
||||||
|
* Metrics endpoint for Prometheus integration
|
||||||
|
|
||||||
## [0.5.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.5.0)
|
## [0.5.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.5.0)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
module server
|
||||||
|
|
||||||
|
import metrics
|
||||||
|
import web
|
||||||
|
|
||||||
|
// v1_metrics serves a Prometheus-compatible metrics endpoint.
|
||||||
|
['/api/v1/metrics'; get; markused]
|
||||||
|
fn (mut app App) v1_metrics() web.Result {
|
||||||
|
if !app.conf.collect_metrics {
|
||||||
|
return app.status(.not_found)
|
||||||
|
}
|
||||||
|
|
||||||
|
mut exporter := metrics.new_prometheus_exporter([0.01, 0.05, 0.1, 0.5, 1, 100])
|
||||||
|
exporter.load(app.collector)
|
||||||
|
|
||||||
|
// TODO stream to connection instead
|
||||||
|
body := exporter.export_to_string() or { return app.status(.internal_server_error) }
|
||||||
|
return app.body(.ok, 'text/plain', body)
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ pub:
|
||||||
base_image string = 'archlinux:base-devel'
|
base_image string = 'archlinux:base-devel'
|
||||||
max_log_age int [empty_default]
|
max_log_age int [empty_default]
|
||||||
log_removal_schedule string = '0 0'
|
log_removal_schedule string = '0 0'
|
||||||
|
collect_metrics bool [empty_default]
|
||||||
}
|
}
|
||||||
|
|
||||||
// cmd returns the cli submodule that handles starting the server
|
// cmd returns the cli submodule that handles starting the server
|
||||||
|
|
|
@ -8,6 +8,7 @@ import util
|
||||||
import db
|
import db
|
||||||
import build { BuildJobQueue }
|
import build { BuildJobQueue }
|
||||||
import cron.expression
|
import cron.expression
|
||||||
|
import metrics
|
||||||
|
|
||||||
const (
|
const (
|
||||||
log_file_name = 'vieter.log'
|
log_file_name = 'vieter.log'
|
||||||
|
@ -100,12 +101,19 @@ pub fn server(conf Config) ! {
|
||||||
util.exit_with_message(1, 'Failed to initialize database: $err.msg()')
|
util.exit_with_message(1, 'Failed to initialize database: $err.msg()')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
collector := if conf.collect_metrics {
|
||||||
|
&metrics.MetricsCollector(metrics.new_default_collector())
|
||||||
|
} else {
|
||||||
|
&metrics.MetricsCollector(metrics.new_null_collector())
|
||||||
|
}
|
||||||
|
|
||||||
mut app := &App{
|
mut app := &App{
|
||||||
logger: logger
|
logger: logger
|
||||||
api_key: conf.api_key
|
api_key: conf.api_key
|
||||||
conf: conf
|
conf: conf
|
||||||
repo: repo
|
repo: repo
|
||||||
db: db
|
db: db
|
||||||
|
collector: collector
|
||||||
job_queue: build.new_job_queue(global_ce, conf.base_image)
|
job_queue: build.new_job_queue(global_ce, conf.base_image)
|
||||||
}
|
}
|
||||||
app.init_job_queue() or {
|
app.init_job_queue() or {
|
||||||
|
|
|
@ -2,6 +2,7 @@ Module {
|
||||||
dependencies: [
|
dependencies: [
|
||||||
'https://git.rustybever.be/vieter-v/conf',
|
'https://git.rustybever.be/vieter-v/conf',
|
||||||
'https://git.rustybever.be/vieter-v/docker',
|
'https://git.rustybever.be/vieter-v/docker',
|
||||||
'https://git.rustybever.be/vieter-v/aur'
|
'https://git.rustybever.be/vieter-v/aur',
|
||||||
|
'https://git.rustybever.be/vieter-v/metrics'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import net.urllib
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
import log
|
import log
|
||||||
|
import metrics
|
||||||
|
|
||||||
// The Context struct represents the Context which hold the HTTP request and response.
|
// The Context struct represents the Context which hold the HTTP request and response.
|
||||||
// It has fields for the query, form, files.
|
// It has fields for the query, form, files.
|
||||||
|
@ -27,6 +28,8 @@ pub mut:
|
||||||
conn &net.TcpConn = unsafe { nil }
|
conn &net.TcpConn = unsafe { nil }
|
||||||
// Gives access to a shared logger object
|
// Gives access to a shared logger object
|
||||||
logger shared log.Log
|
logger shared log.Log
|
||||||
|
// Used to collect metrics on the web server
|
||||||
|
collector &metrics.MetricsCollector
|
||||||
// time.ticks() from start of web connection handle.
|
// time.ticks() from start of web connection handle.
|
||||||
// You can use it to determine how much time is spent on your request.
|
// You can use it to determine how much time is spent on your request.
|
||||||
page_gen_start i64
|
page_gen_start i64
|
||||||
|
@ -145,6 +148,15 @@ pub fn (ctx &Context) is_authenticated() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// body sends the given body as an HTTP response.
|
||||||
|
pub fn (mut ctx Context) body(status http.Status, content_type string, body string) Result {
|
||||||
|
ctx.status = status
|
||||||
|
ctx.content_type = content_type
|
||||||
|
ctx.send_response(body)
|
||||||
|
|
||||||
|
return Result{}
|
||||||
|
}
|
||||||
|
|
||||||
// json<T> HTTP_OK with json_s as payload with content-type `application/json`
|
// json<T> HTTP_OK with json_s as payload with content-type `application/json`
|
||||||
pub fn (mut ctx Context) json<T>(status http.Status, j T) Result {
|
pub fn (mut ctx Context) json<T>(status http.Status, j T) Result {
|
||||||
ctx.status = status
|
ctx.status = status
|
||||||
|
@ -319,6 +331,18 @@ fn handle_conn<T>(mut conn net.TcpConn, mut app T, routes map[string]Route) {
|
||||||
app.logger.flush()
|
app.logger.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record how long request took to process
|
||||||
|
labels := [
|
||||||
|
['method', app.req.method.str()]!,
|
||||||
|
['path', app.req.url]!,
|
||||||
|
['status', app.status.int().str()]!,
|
||||||
|
]
|
||||||
|
app.collector.counter_increment(name: 'http_requests_total', labels: labels)
|
||||||
|
app.collector.histogram_record(time.ticks() - app.page_gen_start,
|
||||||
|
name: 'http_requests_time_ms'
|
||||||
|
labels: labels
|
||||||
|
)
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
free(app)
|
free(app)
|
||||||
}
|
}
|
||||||
|
@ -384,6 +408,7 @@ fn handle_conn<T>(mut conn net.TcpConn, mut app T, routes map[string]Route) {
|
||||||
static_mime_types: app.static_mime_types
|
static_mime_types: app.static_mime_types
|
||||||
reader: reader
|
reader: reader
|
||||||
logger: app.logger
|
logger: app.logger
|
||||||
|
collector: app.collector
|
||||||
api_key: app.api_key
|
api_key: app.api_key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,3 +13,4 @@ api_update_frequency = 2
|
||||||
image_rebuild_frequency = 1
|
image_rebuild_frequency = 1
|
||||||
max_concurrent_builds = 3
|
max_concurrent_builds = 3
|
||||||
max_log_age = 64
|
max_log_age = 64
|
||||||
|
collect_metrics = true
|
||||||
|
|
Loading…
Reference in New Issue