<commit_before>/*
 * moleculer
 * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer)
 * MIT Licensed
 */

"use strict";

const BaseReporter = require("./base");
const _ = require("lodash");
const http = require("http");
const zlib = require("zlib");
const { MoleculerError } = require("../../errors");
const METRIC = require("../constants");

/**
 * Prometheus reporter for Moleculer.
 *
 * 		https://prometheus.io/
 *
 * Running Prometheus & Grafana in Docker:
 *
 * 		git clone https://github.com/vegasbrianc/prometheus.git
 * 		cd prometheus
 *
 * 	Please note, don't forget add your endpoint to static targets in prometheus/prometheus.yml file.
 *  The default port is 3030.
 *
 *     static_configs:
 *       - targets: ['localhost:9090', 'moleculer-hostname:3030']
 *
 *  Start containers:
 *
 * 		docker-compose up -d
 *
 * Grafana dashboard: http://<docker-ip>:3000
 *
 */
class PrometheusReporter extends BaseReporter {

	/**
	 * Constructor of PrometheusReporters
	 * @param {Object} opts
	 * @memberof PrometheusReporter
	 */
	constructor(opts) {
		super(opts);

		this.opts = _.defaultsDeep(this.opts, {
			port: 3030,
			path: "/metrics",
			defaultLabels: (registry) => ({
				namespace: registry.broker.namespace,
				nodeID: registry.broker.nodeID
			})
		});
	}

	/**
	 * Initialize reporter
	 * @param {MetricRegistry} registry
	 * @memberof PrometheusReporter
	 */
	init(registry) {
		super.init(registry);

		this.server = http.createServer();
		this.server.on("request", this.handler.bind(this));
		this.server.listen(this.opts.port, err => {
			if (err) {
				/* istanbul ignore next */
				return this.registry.broker.fatal(new MoleculerError("Prometheus metric reporter listening error: " + err.message));
			}

			this.logger.info(`Prometheus metric reporter listening on http://0.0.0.0:${this.opts.port}${this.opts.path} address.`);
		});
		this.defaultLabels = _.isFunction(this.opts.defaultLabels) ? this.opts.defaultLabels.call(this, registry) : this.opts.defaultLabels;
	}

	/**
	 * HTTP request handler. Support GZip compressing.
	 *
	 * @param {IncomingMessage} req
	 * @param {ServerResponse} res
	 * @memberof PrometheusReporter
	 */
	handler(req, res) {
		if (req.url == this.opts.path) {
			const resHeader = {
				"Content-Type": "text/plain; version=0.0.4; charset=utf-8"
			};

			const content = this.generatePrometheusResponse();
			const compressing = req.headers["accept-encoding"] && req.headers["accept-encoding"].includes("gzip");
			if (compressing) {
				resHeader["Content-Encoding"] = "gzip";
				zlib.gzip(content, (err, buffer) => {
					/* istanbul ignore next */
					if (err) {
						this.logger("Unable to compress response: " + err.message);
						res.writeHead(500);
						res.end(err.message);
					} else {
						res.writeHead(200, resHeader);
						res.end(buffer);
					}
				});
			} else {
				res.writeHead(200, resHeader);
				res.end(content);
			}
		} else {
			res.writeHead(404, http.STATUS_CODES[404], {});
			res.end();
		}
	}

	/**
	 * Generate Prometheus response.
	 * @returns {String}
	 * @memberof PrometheusReporter
	 */
	generatePrometheusResponse() {
		const content = [];

		const val = value => value == null ? "NaN" : value;

		this.registry.store.forEach(metric => {
			// Filtering
			if (!this.matchMetricName(metric.name)) return;
			// Skip datetime metrics (register too much labels)
			if (metric.name.startsWith("os.datetime")) return;

			const metricName = this.formatMetricName(metric.name).replace(/[.-]/g, "_");
			const metricDesc = metric.description ? metric.description : (metric.name + (metric.unit ? ` (${metric.unit})` : ""));
			const metricType = metric.type;

			const snapshot = metric.snapshot();
			if (snapshot.length == 0)
				return;

			switch(metric.type) {
				case METRIC.TYPE_COUNTER:
				case METRIC.TYPE_GAUGE: {
					content.push(`# HELP ${metricName} ${metricDesc}`);
					content.push(`# TYPE ${metricName} ${metricType}`);
					snapshot.forEach(item => {
						const labelStr = this.labelsToStr(item.labels);
						content.push(`${metricName}${labelStr} ${val(item.value)}`);
					});
					content.push("");

					break;
				}
				case METRIC.TYPE_INFO: {
					content.push(`# HELP ${metricName} ${metricDesc}`);
					content.push(`# TYPE ${metricName} gauge`);
					snapshot.forEach(item => {
						const labelStr = this.labelsToStr(item.labels, { value: item.value });
						content.push(`${metricName}${labelStr} 1`);
					});
					content.push("");<commit_msg>Fix hotreload and Prometheus server<commit_after>/*
 * moleculer
 * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer)
 * MIT Licensed
 */

"use strict";

const BaseReporter = require("./base");
const _ = require("lodash");
const http = require("http");
const zlib = require("zlib");
const { MoleculerError } = require("../../errors");
const METRIC = require("../constants");

/**
 * Prometheus reporter for Moleculer.
 *
 * 		https://prometheus.io/
 *
 * Running Prometheus & Grafana in Docker:
 *
 * 		git clone https://github.com/vegasbrianc/prometheus.git
 * 		cd prometheus
 *
 * 	Please note, don't forget add your endpoint to static targets in prometheus/prometheus.yml file.
 *  The default port is 3030.
 *
 *     static_configs:
 *       - targets: ['localhost:9090', 'moleculer-hostname:3030']
 *
 *  Start containers:
 *
 * 		docker-compose up -d
 *
 * Grafana dashboard: http://<docker-ip>:3000
 *
 */
class PrometheusReporter extends BaseReporter {

	/**
	 * Constructor of PrometheusReporters
	 * @param {Object} opts
	 * @memberof PrometheusReporter
	 */
	constructor(opts) {
		super(opts);

		this.opts = _.defaultsDeep(this.opts, {
			port: 3030,
			path: "/metrics",
			defaultLabels: (registry) => ({
				namespace: registry.broker.namespace,
				nodeID: registry.broker.nodeID
			})
		});
	}

	/**
	 * Initialize reporter
	 * @param {MetricRegistry} registry
	 * @memberof PrometheusReporter
	 */
	init(registry) {
		super.init(registry);

		this.server = http.createServer();
		this.server.on("request", this.handler.bind(this));
		this.server.on('error', (err) => {
			// Someone else is using the address
			if (err.code === 'EADDRINUSE' && !err.stack.includes('PrometheusReporter.init')) {
				throw err
			}
			// Silently swallow the error
			// Hot reload doesn't stop the server
			// We're going to reuse the one that was initially created
		});
		this.server.listen(this.opts.port, err => {
			if (err) {
				/* istanbul ignore next */
				return this.registry.broker.fatal(new MoleculerError("Prometheus metric reporter listening error: " + err.message));
			}

			this.logger.info(`Prometheus metric reporter listening on http://0.0.0.0:${this.opts.port}${this.opts.path} address.`);
		});
		this.defaultLabels = _.isFunction(this.opts.defaultLabels) ? this.opts.defaultLabels.call(this, registry) : this.opts.defaultLabels;
	}

	/**
	 * HTTP request handler. Support GZip compressing.
	 *
	 * @param {IncomingMessage} req
	 * @param {ServerResponse} res
	 * @memberof PrometheusReporter
	 */
	handler(req, res) {
		if (req.url == this.opts.path) {
			const resHeader = {
				"Content-Type": "text/plain; version=0.0.4; charset=utf-8"
			};

			const content = this.generatePrometheusResponse();
			const compressing = req.headers["accept-encoding"] && req.headers["accept-encoding"].includes("gzip");
			if (compressing) {
				resHeader["Content-Encoding"] = "gzip";
				zlib.gzip(content, (err, buffer) => {
					/* istanbul ignore next */
					if (err) {
						this.logger("Unable to compress response: " + err.message);
						res.writeHead(500);
						res.end(err.message);
					} else {
						res.writeHead(200, resHeader);
						res.end(buffer);
					}
				});
			} else {
				res.writeHead(200, resHeader);
				res.end(content);
			}
		} else {
			res.writeHead(404, http.STATUS_CODES[404], {});
			res.end();
		}
	}

	/**
	 * Generate Prometheus response.
	 * @returns {String}
	 * @memberof PrometheusReporter
	 */
	generatePrometheusResponse() {
		const content = [];

		const val = value => value == null ? "NaN" : value;

		this.registry.store.forEach(metric => {
			// Filtering
			if (!this.matchMetricName(metric.name)) return;
			// Skip datetime metrics (register too much labels)
			if (metric.name.startsWith("os.datetime")) return;

			const metricName = this.formatMetricName(metric.name).replace(/[.-]/g, "_");
			const metricDesc = metric.description ? metric.description : (metric.name + (metric.unit ? ` (${metric.unit})` : ""));
			const metricType = metric.type;

			const snapshot = metric.snapshot();
			if (snapshot.length == 0)
				return;

			switch (metric.type) {
				case METRIC.TYPE_COUNTER:
				case METRIC.TYPE_GAUGE: {
					content.push(`# HELP ${metricName} ${metricDesc}`);
					content.push(`# TYPE ${metricName} ${metricType}`);
					snapshot.forEach(item => {
						const labelStr = this.labelsToStr(item.labels);
						content.push(`${metricName}${labelStr} ${val(item.value)}`);
					});
					content.push("");

					break;
				}
				case METRIC.TYPE_INFO: {
					content.push(`# HELP ${metricName} ${metricDesc}`);
					content.push(`# TYPE ${metricName} gauge`);
					snapshot.forEach(item => {
						const labelStr = this.labelsToStr(item.labels, { value: item.value });
						content.push(`${metricName}${labelStr} 1`);
					});
					content.push("");
{'fraction_non_alphanumeric': 0.16120422760755845, 'fraction_numerical': 0.01046226112949717, 'mean_word_length': 3.3551836355183635, 'pattern_counts': {'":': 2, '<': 5, '<?xml version=': 0, '>': 20, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2214599', 'n_tokens_mistral': 3630, 'n_tokens_neox': 3020, 'n_words': 626}