From 72c13196b784c06101f9ed66b5e6c096df30e9d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20F=C3=A4cke?= Date: Sun, 16 Jun 2019 11:28:51 +0200 Subject: [PATCH] improve performance of registry.metrics() --- lib/counter.js | 88 ++++++------ lib/formatter.js | 75 ++++++++++ lib/gauge.js | 83 ++++++----- lib/histogram.js | 86 ++++++------ lib/metric.js | 27 ++++ lib/registry.js | 54 ++------ lib/summary.js | 95 ++++++------- lib/util.js | 11 ++ package-lock.json | 13 ++ package.json | 1 + test/registerTest.js | 320 +++++++++++++++++++++++++------------------ 11 files changed, 483 insertions(+), 370 deletions(-) create mode 100644 lib/formatter.js create mode 100644 lib/metric.js diff --git a/lib/counter.js b/lib/counter.js index 78f93bbe..d75181d2 100644 --- a/lib/counter.js +++ b/lib/counter.js @@ -21,8 +21,9 @@ const { validateMetricName, validateLabelName } = require('./validation'); +const Metric = require('./metric'); -class Counter { +class Counter extends Metric { /** * Counter * @param {string} name - Name of the metric @@ -30,55 +31,8 @@ class Counter { * @param {Array.} labels - Labels */ constructor(name, help, labels) { - let config; - if (isObject(name)) { - config = Object.assign( - { - labelNames: [] - }, - name - ); - - if (!config.registers) { - config.registers = [globalRegistry]; - } - } else { - printDeprecationObjectConstructor(); - - config = { - name, - help, - labelNames: labels, - registers: [globalRegistry] - }; - } - - if (!config.help) { - throw new Error('Missing mandatory help parameter'); - } - if (!config.name) { - throw new Error('Missing mandatory name parameter'); - } - if (!validateMetricName(config.name)) { - throw new Error('Invalid metric name'); - } - - if (!validateLabelName(config.labelNames)) { - throw new Error('Invalid label name'); - } - - this.name = config.name; - - this.labelNames = config.labelNames || []; - + super(getConfig(name, help, labels), type); this.reset(); - - this.help = config.help; - this.aggregator = config.aggregator || 'sum'; - - config.registers.forEach(registryInstance => - registryInstance.registerMetric(this) - ); } /** @@ -177,4 +131,40 @@ function setValue(hashMap, value, timestamp, labels, hash) { return hashMap; } +function getConfig(name, help, labels) { + let config; + if (isObject(name)) { + config = Object.assign( + { + labelNames: [] + }, + name + ); + if (!config.registers) { + config.registers = [globalRegistry]; + } + } else { + printDeprecationObjectConstructor(); + config = { + name, + help, + labelNames: labels, + registers: [globalRegistry] + }; + } + if (!config.help) { + throw new Error('Missing mandatory help parameter'); + } + if (!config.name) { + throw new Error('Missing mandatory name parameter'); + } + if (!validateMetricName(config.name)) { + throw new Error('Invalid metric name'); + } + if (!validateLabelName(config.labelNames)) { + throw new Error('Invalid label name'); + } + return config; +} + module.exports = Counter; diff --git a/lib/formatter.js b/lib/formatter.js new file mode 100644 index 00000000..dcdbbed6 --- /dev/null +++ b/lib/formatter.js @@ -0,0 +1,75 @@ +'use strict'; + +const generateFunction = require('generate-function'); + +function getFormatter(metric, defaultLabels) { + const defaultLabelNames = Object.keys(defaultLabels); + const name = escapeString(metric.name); + const help = escapeString(metric.help); + const type = metric.type; + const labelsCode = getLabelsCode(metric, defaultLabelNames, defaultLabels); + + const gen = generateFunction(); + gen(` + function format(item, escapeLabelValue, getValueAsString, timestamps) { + const info = '# HELP ${name} ${help}\\n# TYPE ${name} ${type}\\n'; + let values = ''; + for (const val of item.values || []) { + val.labels = val.labels || {}; + let metricName = val.metricName || '${name}'; + const labels = \`${labelsCode}\`; + const hasLabels = labels.length > 0; + metricName += \`\${hasLabels ? '{' : ''}\${labels}\${hasLabels ? '}' : ''}\`; + let line = \`\${metricName} \${getValueAsString(val.value)}\`; + if (timestamps && val.timestamp) line += \` \${val.timestamp}\`; + values += \`\${line}\\n\`; + } + return info + values; + }`); + return gen.toFunction(); +} + +function getLabelsCode(metric, defaultLabelNames, defaultLabels) { + let labelString = ''; + const labelNames = getLabelNames(metric, defaultLabelNames); + for (let index = 0; index < labelNames.length; index++) { + const labelName = labelNames[index]; + if (labelName === 'quantile') { + labelString += `\${val.labels.quantile != null ? \`quantile="\${escapeLabelValue(val.labels.quantile)}"\` : ''}`; + } else if (labelName === 'le') { + labelString += `\${val.labels.le != null ? \`le="\${escapeLabelValue(val.labels.le)}"\` : ''}`; + } else { + labelString += `${labelName}="\${val.labels['${labelName}'] ? escapeLabelValue(val.labels['${labelName}']) : '${ + defaultLabels[labelName] + }'}"`; + } + if (index !== labelNames.length - 1 && labelString.length > 0) { + labelString += ','; + } + } + return labelString; +} + +function getLabelNames(metric, defaultLabelNames) { + const labelNames = [...metric.labelNames]; + for (const labelName of defaultLabelNames) { + if (!labelNames.includes(labelName)) { + labelNames.push(labelName); + } + } + if (metric.type === 'summary') { + labelNames.push('quantile'); + } + if (metric.type === 'histogram') { + labelNames.push('le'); + } + return labelNames; +} + +function escapeString(str) { + return str.replace(/\n/g, '\\\\n').replace(/\\(?!n)/g, '\\\\\\\\'); +} + +module.exports = { + getFormatter +}; diff --git a/lib/gauge.js b/lib/gauge.js index 0ab0a8d1..dada5344 100644 --- a/lib/gauge.js +++ b/lib/gauge.js @@ -22,8 +22,9 @@ const { validateLabel, validateLabelName } = require('./validation'); +const Metric = require('./metric'); -class Gauge { +class Gauge extends Metric { /** * Gauge * @param {string} name - Name of the metric @@ -31,50 +32,8 @@ class Gauge { * @param {Array.} labels - Array with strings, all label keywords supported */ constructor(name, help, labels) { - let config; - if (isObject(name)) { - config = Object.assign( - { - labelNames: [] - }, - name - ); - - if (!config.registers) { - config.registers = [globalRegistry]; - } - } else { - printDeprecationObjectConstructor(); - config = { - name, - help, - labelNames: labels, - registers: [globalRegistry] - }; - } - - if (!config.help) { - throw new Error('Missing mandatory help parameter'); - } - if (!config.name) { - throw new Error('Missing mandatory name parameter'); - } - if (!validateMetricName(config.name)) { - throw new Error('Invalid metric name'); - } - if (!validateLabelName(config.labelNames)) { - throw new Error('Invalid label name'); - } - - this.name = config.name; - this.labelNames = config.labelNames || []; + super(getConfig(name, help, labels), type); this.reset(); - this.help = config.help; - this.aggregator = config.aggregator || 'sum'; - - config.registers.forEach(registryInstance => - registryInstance.registerMetric(this) - ); } /** @@ -261,4 +220,40 @@ function convertLabelsAndValues(labels, value) { }; } +function getConfig(name, help, labels) { + let config; + if (isObject(name)) { + config = Object.assign( + { + labelNames: [] + }, + name + ); + if (!config.registers) { + config.registers = [globalRegistry]; + } + } else { + printDeprecationObjectConstructor(); + config = { + name, + help, + labelNames: labels, + registers: [globalRegistry] + }; + } + if (!config.help) { + throw new Error('Missing mandatory help parameter'); + } + if (!config.name) { + throw new Error('Missing mandatory name parameter'); + } + if (!validateMetricName(config.name)) { + throw new Error('Invalid metric name'); + } + if (!validateLabelName(config.labelNames)) { + throw new Error('Invalid label name'); + } + return config; +} + module.exports = Gauge; diff --git a/lib/histogram.js b/lib/histogram.js index 826a0160..04ae52ea 100644 --- a/lib/histogram.js +++ b/lib/histogram.js @@ -19,8 +19,9 @@ const { validateLabel, validateLabelName } = require('./validation'); +const Metric = require('./metric'); -class Histogram { +class Histogram extends Metric { /** * Histogram * @param {string} name - Name of the metric @@ -29,48 +30,9 @@ class Histogram { * @param {object} conf - Configuration object */ constructor(name, help, labelsOrConf, conf) { - let config; - - if (isObject(name)) { - config = Object.assign( - { - buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], - labelNames: [] - }, - name - ); - - if (!config.registers) { - config.registers = [globalRegistry]; - } - } else { - let obj; - let labels = []; - - if (Array.isArray(labelsOrConf)) { - obj = conf || {}; - labels = labelsOrConf; - } else { - obj = labelsOrConf || {}; - } - - printDeprecationObjectConstructor(); - - config = { - name, - labelNames: labels, - help, - buckets: configureUpperbounds(obj.buckets), - registers: [globalRegistry] - }; - } - validateInput(config.name, config.help, config.labelNames); - - this.name = config.name; - this.help = config.help; - this.aggregator = config.aggregator || 'sum'; + super(getConfig(name, labelsOrConf, conf, help), type); - this.upperBounds = config.buckets; + this.upperBounds = this.config.buckets; this.bucketValues = this.upperBounds.reduce((acc, upperBound) => { acc[upperBound] = 0; return acc; @@ -82,7 +44,6 @@ class Histogram { this.count = 0; this.hashMap = {}; - this.labelNames = config.labelNames || []; if (this.labelNames.length === 0) { this.hashMap = { @@ -92,10 +53,6 @@ class Histogram { ) }; } - - config.registers.forEach(registryInstance => - registryInstance.registerMetric(this) - ); } /** @@ -320,4 +277,39 @@ function addSumAndCountForExport(histogram) { }; } +function getConfig(name, labelsOrConf, conf, help) { + let config; + if (isObject(name)) { + config = Object.assign( + { + buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + labelNames: [] + }, + name + ); + if (!config.registers) { + config.registers = [globalRegistry]; + } + } else { + let obj; + let labels = []; + if (Array.isArray(labelsOrConf)) { + obj = conf || {}; + labels = labelsOrConf; + } else { + obj = labelsOrConf || {}; + } + printDeprecationObjectConstructor(); + config = { + name, + labelNames: labels, + help, + buckets: configureUpperbounds(obj.buckets), + registers: [globalRegistry] + }; + } + validateInput(config.name, config.help, config.labelNames); + return config; +} + module.exports = Histogram; diff --git a/lib/metric.js b/lib/metric.js new file mode 100644 index 00000000..617f2e55 --- /dev/null +++ b/lib/metric.js @@ -0,0 +1,27 @@ +'use strict'; + +const { getValueAsString, escapeLabelValue } = require('./util'); +const { getFormatter } = require('./formatter'); + +module.exports = class Metric { + constructor(config, type) { + this.config = config; + this.type = type; + this.name = config.name; + this.help = config.help; + this.labelNames = this.config.labelNames || []; + this.aggregator = this.config.aggregator || 'sum'; + + this.config.registers.forEach(registryInstance => + registryInstance.registerMetric(this) + ); + } + + getPrometheusString(timestamps, defaultLabels) { + const item = this.get(); + if (!this.formatter) { + this.formatter = getFormatter(this, defaultLabels); + } + return this.formatter(item, escapeLabelValue, getValueAsString, timestamps); + } +}; diff --git a/lib/registry.js b/lib/registry.js index 4946cacc..77fe59f2 100644 --- a/lib/registry.js +++ b/lib/registry.js @@ -1,15 +1,6 @@ 'use strict'; -const { getValueAsString } = require('./util'); -function escapeString(str) { - return str.replace(/\n/g, '\\n').replace(/\\(?!n)/g, '\\\\'); -} -function escapeLabelValue(str) { - if (typeof str !== 'string') { - return str; - } - return escapeString(str).replace(/"/g, '\\"'); -} +const { escapeLabelValue } = require('./util'); const defaultMetricsOpts = { timestamps: true @@ -25,47 +16,17 @@ class Registry { return Object.keys(this._metrics).map(this.getSingleMetric, this); } - getMetricAsPrometheusString(metric, conf) { - const opts = Object.assign({}, defaultMetricsOpts, conf); - const item = metric.get(); - const name = escapeString(item.name); - const help = `# HELP ${name} ${escapeString(item.help)}`; - const type = `# TYPE ${name} ${item.type}`; - const defaultLabelNames = Object.keys(this._defaultLabels); - - let values = ''; - for (const val of item.values || []) { - val.labels = val.labels || {}; - for (const labelName of defaultLabelNames) { - val.labels[labelName] = - val.labels[labelName] || this._defaultLabels[labelName]; - } - - let labels = ''; - for (const key of Object.keys(val.labels)) { - labels += `${key}="${escapeLabelValue(val.labels[key])}",`; - } - - let metricName = val.metricName || item.name; - if (labels) { - metricName += `{${labels.substring(0, labels.length - 1)}}`; - } - - let line = `${metricName} ${getValueAsString(val.value)}`; - if (opts.timestamps && val.timestamp) { - line += ` ${val.timestamp}`; - } - values += `${line.trim()}\n`; - } - - return `${help}\n${type}\n${values}`.trim(); + getMetricAsPrometheusString(metric, conf = {}) { + const timestamps = + conf.timestamps === undefined ? defaultMetricsOpts : conf.timestamps; + return metric.getPrometheusString(timestamps, this._defaultLabels); } metrics(opts) { let metrics = ''; for (const metric of this.getMetricsAsArray()) { - metrics += `${this.getMetricAsPrometheusString(metric, opts)}\n\n`; + metrics += `${this.getMetricAsPrometheusString(metric, opts)}\n`; } return metrics.substring(0, metrics.length - 1); @@ -125,6 +86,9 @@ class Registry { setDefaultLabels(labels) { this._defaultLabels = labels; + for (const name of Object.keys(this._defaultLabels)) { + this._defaultLabels[name] = escapeLabelValue(this._defaultLabels[name]); + } } resetMetrics() { diff --git a/lib/summary.js b/lib/summary.js index f21256dc..7e925e27 100644 --- a/lib/summary.js +++ b/lib/summary.js @@ -20,8 +20,9 @@ const { validateLabelName } = require('./validation'); const timeWindowQuantiles = require('./timeWindowQuantiles'); +const Metric = require('./metric'); -class Summary { +class Summary extends Metric { /** * Summary * @param {string} name - Name of the metric @@ -30,55 +31,13 @@ class Summary { * @param {object} conf - Configuration object */ constructor(name, help, labelsOrConf, conf) { - let config; - if (isObject(name)) { - config = Object.assign( - { - percentiles: [0.01, 0.05, 0.5, 0.9, 0.95, 0.99, 0.999], - labelNames: [] - }, - name - ); - - if (!config.registers) { - config.registers = [globalRegistry]; - } - } else { - let obj; - let labels = []; - - if (Array.isArray(labelsOrConf)) { - obj = conf || {}; - labels = labelsOrConf; - } else { - obj = labelsOrConf || {}; - } - - printDeprecationObjectConstructor(); - - config = { - name, - help, - labelNames: labels, - percentiles: configurePercentiles(obj.percentiles), - registers: [globalRegistry], - maxAgeSeconds: obj.maxAgeSeconds, - ageBuckets: obj.ageBuckets - }; - } + super(getConfig(name, labelsOrConf, conf, help), type); - validateInput(config.name, config.help, config.labelNames); + this.maxAgeSeconds = this.config.maxAgeSeconds; + this.ageBuckets = this.config.ageBuckets; - this.maxAgeSeconds = config.maxAgeSeconds; - this.ageBuckets = config.ageBuckets; - - this.name = config.name; - this.help = config.help; - this.aggregator = config.aggregator || 'sum'; - - this.percentiles = config.percentiles; + this.percentiles = this.config.percentiles; this.hashMap = {}; - this.labelNames = config.labelNames || []; if (this.labelNames.length === 0) { this.hashMap = { @@ -90,10 +49,6 @@ class Summary { } }; } - - config.registers.forEach(registryInstance => - registryInstance.registerMetric(this) - ); } /** @@ -281,4 +236,42 @@ function convertLabelsAndValues(labels, value) { }; } +function getConfig(name, labelsOrConf, conf, help) { + let config; + if (isObject(name)) { + config = Object.assign( + { + percentiles: [0.01, 0.05, 0.5, 0.9, 0.95, 0.99, 0.999], + labelNames: [] + }, + name + ); + if (!config.registers) { + config.registers = [globalRegistry]; + } + } else { + let obj; + let labels = []; + if (Array.isArray(labelsOrConf)) { + obj = conf || {}; + labels = labelsOrConf; + } else { + obj = labelsOrConf || {}; + } + printDeprecationObjectConstructor(); + config = { + name, + help, + labelNames: labels, + percentiles: configurePercentiles(obj.percentiles), + registers: [globalRegistry], + maxAgeSeconds: obj.maxAgeSeconds, + ageBuckets: obj.ageBuckets + }; + } + + validateInput(config.name, config.help, config.labelNames); + return config; +} + module.exports = Summary; diff --git a/lib/util.js b/lib/util.js index 36253cc2..5296d2be 100644 --- a/lib/util.js +++ b/lib/util.js @@ -4,6 +4,17 @@ const deprecationsEmitted = {}; exports.isDate = isDate; +exports.escapeString = function(str) { + return str.replace(/\n/g, '\\n').replace(/\\(?!n)/g, '\\\\'); +}; + +exports.escapeLabelValue = function(str) { + if (typeof str !== 'string') { + return str; + } + return exports.escapeString(str).replace(/"/g, '\\"'); +}; + exports.getPropertiesFromObj = function(hashMap) { const obj = Object.keys(hashMap).map(x => hashMap[x]); return obj; diff --git a/package-lock.json b/package-lock.json index 99ee1783..67057391 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3169,6 +3169,14 @@ "wide-align": "^1.1.0" } }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "requires": { + "is-property": "^1.0.2" + } + }, "genfun": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", @@ -3829,6 +3837,11 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" + }, "is-regex": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", diff --git a/package.json b/package.json index 08ae2be9..43841aec 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "typescript": "^3.0.3" }, "dependencies": { + "generate-function": "^2.3.1", "tdigest": "^0.1.1" }, "types": "./index.d.ts", diff --git a/test/registerTest.js b/test/registerTest.js index 64c6f109..9d5a6d06 100644 --- a/test/registerTest.js +++ b/test/registerTest.js @@ -1,5 +1,7 @@ 'use strict'; +const Metric = require('../lib/metric'); + describe('register', () => { const register = require('../index').register; const Counter = require('../index').Counter; @@ -45,95 +47,114 @@ describe('register', () => { }); it('should handle and output a metric with a NaN value', () => { - register.registerMetric({ - get() { - return { - name: 'test_metric', - type: 'gauge', - help: 'A test metric', - values: [ - { - value: NaN - } - ] - }; - } - }); + const metric = new Metric( + { + registers: [], + name: 'test_metric', + help: 'A test metric' + }, + 'gauge' + ); + metric.get = () => { + return { + name: 'test_metric', + type: 'gauge', + help: 'A test metric', + values: [{ value: NaN }] + }; + }; + register.registerMetric(metric); const lines = register.metrics().split('\n'); expect(lines).toHaveLength(4); expect(lines[2]).toEqual('test_metric Nan'); }); it('should handle and output a metric with an +Infinity value', () => { - register.registerMetric({ - get() { - return { - name: 'test_metric', - type: 'gauge', - help: 'A test metric', - values: [ - { - value: Infinity - } - ] - }; - } - }); + const metric = new Metric( + { + registers: [], + name: 'test_metric', + help: 'A test metric' + }, + 'gauge' + ); + metric.get = () => { + return { + name: 'test_metric', + type: 'gauge', + help: 'A test metric', + values: [{ value: Infinity }] + }; + }; + register.registerMetric(metric); const lines = register.metrics().split('\n'); expect(lines).toHaveLength(4); expect(lines[2]).toEqual('test_metric +Inf'); }); it('should handle and output a metric with an -Infinity value', () => { - register.registerMetric({ - get() { - return { - name: 'test_metric', - type: 'gauge', - help: 'A test metric', - values: [ - { - value: -Infinity - } - ] - }; - } - }); + const metric = new Metric( + { + registers: [], + name: 'test_metric', + help: 'A test metric' + }, + 'gauge' + ); + metric.get = () => { + return { + name: 'test_metric', + type: 'gauge', + help: 'A test metric', + values: [{ value: -Infinity }] + }; + }; + register.registerMetric(metric); const lines = register.metrics().split('\n'); expect(lines).toHaveLength(4); expect(lines[2]).toEqual('test_metric -Inf'); }); it('should handle a metric without labels', () => { - register.registerMetric({ - get() { - return { - name: 'test_metric', - type: 'counter', - help: 'A test metric', - values: [ - { - value: 1 - } - ] - }; - } - }); + const metric = new Metric( + { + registers: [], + name: 'test_metric', + help: 'A test metric' + }, + 'counter' + ); + metric.get = () => { + return { + name: 'test_metric', + type: 'counter', + help: 'A test metric', + values: [{ value: 1 }] + }; + }; + register.registerMetric(metric); expect(register.metrics().split('\n')).toHaveLength(4); }); it('should handle a metric with default labels', () => { register.setDefaultLabels({ testLabel: 'testValue' }); - register.registerMetric({ - get() { - return { - name: 'test_metric', - type: 'counter', - help: 'A test metric', - values: [{ value: 1 }] - }; - } - }); + const metric = new Metric( + { + registers: [], + name: 'test_metric', + help: 'A test metric' + }, + 'counter' + ); + metric.get = () => { + return { + name: 'test_metric', + type: 'counter', + help: 'A test metric', + values: [{ value: 1 }] + }; + }; + register.registerMetric(metric); const output = register.metrics().split('\n')[2]; expect(output).toEqual('test_metric{testLabel="testValue"} 1'); @@ -141,24 +162,33 @@ describe('register', () => { it('labeled metrics should take precidence over defaulted', () => { register.setDefaultLabels({ testLabel: 'testValue' }); - register.registerMetric({ - get() { - return { - name: 'test_metric', - type: 'counter', - help: 'A test metric', - values: [ - { - value: 1, - labels: { - testLabel: 'overlapped', - anotherLabel: 'value123' - } + + const metric = new Metric( + { + registers: [], + name: 'test_metric', + help: 'A test metric', + labelNames: ['testLabel', 'anotherLabel'] + }, + 'counter' + ); + metric.get = () => { + return { + name: 'test_metric', + type: 'counter', + help: 'A test metric', + values: [ + { + value: 1, + labels: { + testLabel: 'overlapped', + anotherLabel: 'value123' } - ] - }; - } - }); + } + ] + }; + }; + register.registerMetric(metric); expect(register.metrics().split('\n')[2]).toEqual( 'test_metric{testLabel="overlapped",anotherLabel="value123"} 1' @@ -186,15 +216,22 @@ describe('register', () => { describe('should escape', () => { let escapedResult; beforeEach(() => { - register.registerMetric({ - get() { - return { - name: 'test_"_\\_\n_metric', - help: 'help_help', - type: 'counter' - }; - } - }); + const metric = new Metric( + { + registers: [], + name: 'test_"_\\_\n_metric', + help: 'help_help' + }, + 'counter' + ); + metric.get = () => { + return { + name: 'test_"_\\_\n_metric', + help: 'help_help', + type: 'counter' + }; + }; + register.registerMetric(metric); escapedResult = register.metrics(); }); it('backslash to \\\\', () => { @@ -206,24 +243,32 @@ describe('register', () => { }); it('should escape " in label values', () => { - register.registerMetric({ - get() { - return { - name: 'test_metric', - type: 'counter', - help: 'A test metric', - values: [ - { - value: 12, - labels: { - label: 'hello', - code: '3"03' - } + const metric = new Metric( + { + registers: [], + name: 'test_metric', + help: 'A test metric', + labelNames: ['label', 'code'] + }, + 'counter' + ); + metric.get = () => { + return { + name: 'test_metric', + type: 'counter', + help: 'A test metric', + values: [ + { + value: 12, + labels: { + label: 'hello', + code: '3"03' } - ] - }; - } - }); + } + ] + }; + }; + register.registerMetric(metric); const escapedResult = register.metrics(); expect(escapedResult).toMatch(/\\"/); }); @@ -371,32 +416,39 @@ describe('register', () => { function getMetric(name) { name = name || 'test_metric'; - return { - name, - get() { - return { - name, - type: 'counter', - help: 'A test metric', - values: [ - { - value: 12, - labels: { - label: 'hello', - code: '303' - } - }, - { - value: 34, - timestamp: 1485392700000, - labels: { - label: 'bye', - code: '404' - } + const metric = new Metric( + { + name, + registers: [], + help: 'A test metric', + labelNames: ['label', 'code'] + }, + 'counter' + ); + metric.get = () => { + return { + name, + type: 'counter', + help: 'A test metric', + values: [ + { + value: 12, + labels: { + label: 'hello', + code: '303' } - ] - }; - } + }, + { + value: 34, + timestamp: 1485392700000, + labels: { + label: 'bye', + code: '404' + } + } + ] + }; }; + return metric; } });