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#325remotes/1739821333615734048/dev
						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