Comparing version 1.4.1 to 2.0.0
@@ -6,2 +6,12 @@ # Changelog | ||
## 2.0.0 [backward incompatible if you use histograms] | ||
* #17 Histogram buckets completely changed to be compatible with Prometheus. You | ||
can now either pass in an array of bucket values. If you do not pass in bucket | ||
values, you will get the default buckets: | ||
``` | ||
[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] | ||
``` | ||
* Important: see [docs/migrating.md](docs/migrating.md) for details on migrating | ||
from node-artedi v1.x to v2.x. | ||
## 1.4.1 | ||
@@ -8,0 +18,0 @@ * #15 improve the performance of hashObj() |
@@ -6,3 +6,3 @@ /* | ||
* | ||
* Copyright (c) 2017, Joyent, Inc. | ||
* Copyright (c) 2018, Joyent, Inc. | ||
*/ | ||
@@ -18,2 +18,3 @@ | ||
var lib_utils = require('./utils'); | ||
var lib_buckets = require('./buckets'); | ||
var lib_counter = require('./counter'); | ||
@@ -79,2 +80,11 @@ var lib_gauge = require('./gauge'); | ||
/* | ||
* Indicates this version uses fixed buckets per node-artedi#17 instead of | ||
* the previous versions that supported dynamic buckets. We add this to the | ||
* collector so that things like node-fast can support both node-artedi v1 | ||
* and node-artedi v2 by checking whether this is true on an otherwise | ||
* unknown `collector` object. | ||
*/ | ||
this.FIXED_BUCKETS = true; | ||
this.registry = {}; | ||
@@ -353,4 +363,7 @@ this.triggerRegistry = []; | ||
}, | ||
exponentialBuckets: lib_buckets.exponentialBuckets, | ||
FMT_PROM: FMT_PROM, | ||
FMT_PROM_0_0_4: FMT_PROM_0_0_4 | ||
FMT_PROM_0_0_4: FMT_PROM_0_0_4, | ||
linearBuckets: lib_buckets.linearBuckets, | ||
logLinearBuckets: lib_buckets.logLinearBuckets | ||
}; |
@@ -20,3 +20,23 @@ /* | ||
/* | ||
* These default buckets match the official golang, javascript, rust and other | ||
* client libraries. | ||
*/ | ||
var DEFAULT_BUCKETS = [ | ||
0.005, | ||
0.01, | ||
0.025, | ||
0.05, | ||
0.1, | ||
0.25, | ||
0.5, | ||
1, | ||
2.5, | ||
5, | ||
10 | ||
]; | ||
/* | ||
* A Histogram is a type of collector that represents a series of Counters. Each | ||
@@ -29,5 +49,9 @@ * Counter corresponds to a certain range of values, called 'buckets.' | ||
mod_assert.string(options.help, 'options.help'); | ||
mod_assert.optionalArrayOfNumber(options.buckets, 'options.buckets'); | ||
mod_assert.optionalObject(options.labels, 'options.labels'); | ||
mod_assert.optionalObject(options.parentLabels, 'options.parentLabels'); | ||
var i; | ||
var prevBucket = -1; | ||
this.staticLabels = | ||
@@ -40,4 +64,13 @@ mod_jsprim.mergeObjects(options.parentLabels, options.labels, null); | ||
this.buckets = options.buckets || DEFAULT_BUCKETS; | ||
this.counters = {}; | ||
this.gauge = new lib_gauge.Gauge(options); | ||
// Assert that buckets are monotonic | ||
for (i = 0; i < this.buckets.length; i++) { | ||
mod_assert.ok(this.buckets[i] > prevBucket, | ||
'buckets should be monotonic [' + this.buckets[i] + | ||
' > ' + prevBucket + ']'); | ||
prevBucket = this.buckets[i]; | ||
} | ||
} | ||
@@ -64,14 +97,6 @@ | ||
var i; | ||
var counter; | ||
var pairCopy; | ||
var buckets; | ||
var index, count, i, bucket; | ||
var smaller; | ||
var metric; | ||
// For log-linear bucketing, we will produce five linear steps per log jump. | ||
// At a point in the future, we may allow the user to provide this value, | ||
// but five seems like a reasonable default. | ||
var linearSteps = 5; | ||
pairs = mod_jsprim.mergeObjects(pairs, this.staticLabels, null); | ||
@@ -83,69 +108,12 @@ counter = this.labels(pairs); | ||
/* Begin setting initial value for new buckets (if applicable). */ | ||
// Determine which bucket from the new order our value falls into. | ||
buckets = getOrder(value, linearSteps); | ||
if (!buckets) { | ||
// The value passed in is too big (> 10 billion), so we just increment | ||
// the +Inf counter, and add to the Gauge. | ||
counter.increment({ | ||
le: '+Inf' | ||
}); | ||
this.gauge.add(value, counter.staticLabels); | ||
return; | ||
} | ||
// Find the largest bucket that the observed value falls into. | ||
for (bucket in buckets) { | ||
if (value <= buckets[bucket]) { | ||
index = buckets[bucket]; | ||
break; | ||
} | ||
} | ||
// Find the next-smallest bucket from the list of already-used buckets. | ||
// The buckets are sorted when they are added to a metric vector. | ||
for (bucket in counter.metricVec.buckets) { | ||
if (counter.metricVec.buckets[bucket] < index) { | ||
smaller = bucket; | ||
} | ||
} | ||
// Check to see if the proper bucket for this value already exists in | ||
// the bucket list. | ||
if (counter.metricVec.buckets.indexOf(index) === -1) { | ||
counter.metricVec.addBuckets(buckets); | ||
if (smaller) { | ||
// Copy value from the next-smallest bucket into the newly created | ||
// buckets. | ||
pairCopy['le'] = counter.metricVec.buckets[smaller]; | ||
count = counter.labels(pairCopy).value; | ||
if (count > 0) { | ||
for (bucket in buckets) { | ||
pairCopy['le'] = buckets[bucket]; | ||
metric = counter.labels(pairCopy); | ||
if (metric.value === 0) { | ||
// We don't want to double the value of overlapping | ||
// buckets. | ||
counter.add(count, pairCopy); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
/* Done setting initial value for new buckets. */ | ||
// Now we need to increment the Counters for the buckets >= the value | ||
// passed in. | ||
buckets = counter.metricVec.buckets; | ||
index = buckets.indexOf(index); | ||
for (i = 0; i < buckets.length; i++) { | ||
pairCopy['le'] = buckets[i]; | ||
if (i < index) { | ||
// We don't need to increment buckets that are smaller than what we | ||
// received, but we do want to zero them out if they don't exist. | ||
// Increment the counters for each bucket(le) where "value" is <= the bucket | ||
for (i = 0; i < this.buckets.length; i++) { | ||
pairCopy['le'] = this.buckets[i]; | ||
if (value <= this.buckets[i]) { | ||
counter.increment(pairCopy); | ||
} else { | ||
// We don't need to increment buckets that are smaller than value, | ||
// but we do want to zero them out if they don't exist. | ||
counter.labels(pairCopy); | ||
continue; | ||
} | ||
counter.increment(pairCopy); | ||
} | ||
@@ -296,62 +264,4 @@ | ||
/* | ||
* Find (and return) a list of linear numbers that 'value' falls into. | ||
* The 'steps' argument is provided, as we may allow the user to specify | ||
* the number of linear 'steps' between logarithmic jumps. This idea is taken | ||
* from DTrace's log/linear quantization ('llquantize()'). | ||
* | ||
* The 'steps' argument is ignored for retrieving the order of values less than | ||
* one. | ||
*/ | ||
function getOrder(value, steps) { | ||
var i, j, bucketVal, width, next; | ||
var buckets; | ||
// These values are relatively arbitrary. It's possible that we may want to | ||
// allow the user to change these at some point. | ||
var factor = 10; | ||
var low = 0; | ||
// This is an arbitrary high watermark. Setting this allows us to ensure | ||
// that our loop will always exit. | ||
// The maximum value that we can observe (without it falling into only the | ||
// +Inf bucket) is 3,486,784,401 | ||
var high = 13; | ||
bucketVal = 0.0001; // smallest possible precision. | ||
for (i = low; i <= high; i++) { | ||
buckets = []; | ||
next = bucketVal * factor; | ||
if (bucketVal < 1) { | ||
// Ignore the 'step' count for very small values. | ||
width = bucketVal; | ||
} else { | ||
// Otherwise enforce that we don't have more than 'step' buckets. | ||
width = next > steps ? next / steps : 1; | ||
} | ||
for (j = 0; bucketVal <= next; bucketVal += width, j++) { | ||
if (bucketVal < 10) { | ||
// only keep a few decimal places when decimal precision matters | ||
bucketVal = +(bucketVal.toFixed(4)); | ||
} else { | ||
bucketVal = Math.ceil(bucketVal); | ||
} | ||
buckets[j] = bucketVal; | ||
} | ||
// Overlap buckets so we get something like: | ||
// [0-10], [10-100], [100-1000]. | ||
bucketVal -= width; | ||
if (value <= buckets[buckets.length - 1]) { | ||
// The number is within this order. | ||
return (buckets); | ||
} | ||
} | ||
return (null); | ||
} | ||
module.exports = { | ||
Histogram: Histogram | ||
}; |
{ | ||
"name": "artedi", | ||
"version": "1.4.1", | ||
"version": "2.0.0", | ||
"description": "a metric client library", | ||
@@ -5,0 +5,0 @@ "main": "lib/collector.js", |
@@ -42,8 +42,9 @@ # node-artedi: client library for metric collection | ||
var histogram = collector.histogram({ | ||
name: 'http_request_latency_ms', | ||
help: 'latency of muskie http requests' | ||
name: 'http_request_latency_seconds', | ||
help: 'latency of muskie http requests', | ||
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] | ||
}); | ||
// Observe a latency of 998ms for a 'putobjectdir' request. | ||
histogram.observe(998, { | ||
histogram.observe(0.998, { | ||
method: 'putobjectdir' | ||
@@ -63,13 +64,19 @@ }); | ||
// # TYPE http_requests_completed counter | ||
// http_requests_completed{zone="e5d3",method="getobject",code="200"} 1 | ||
// # HELP http_request_latency_ms latency of muskie http requests | ||
// # TYPE http_request_latency_ms histogram | ||
// http_request_latency_ms{zone="e5d3",method="getobject",code="200",le="729"} 0 | ||
// http_request_latency_ms{zone="e5d3",method="getobject",code="200",le="2187"} 1 | ||
// http_request_latency_ms{zone="e5d3",method="getobject",code="200",le="3645"} 1 | ||
// http_request_latency_ms{zone="e5d3",method="getobject",code="200",le="5103"} 1 | ||
// http_request_latency_ms{zone="e5d3",method="getobject",code="200",le="6561"} 1 | ||
// http_request_latency_ms{zone="e5d3",method="getobject",code="200",le="+Inf"} 1 | ||
// http_request_latency_ms_count{zone="e5d3",method="getobject",code="200"} 1 | ||
// http_request_latency_ms_sum{zone="e5d3",method="getobject",code="200"} 998 | ||
// http_requests_completed{method="getobject",code="200",zone="e5d3"} 1 | ||
// # HELP http_request_latency_seconds latency of muskie http requests | ||
// # TYPE http_request_latency_seconds histogram | ||
// http_request_latency_seconds{method="putobjectdir",le="0.005"} 0 | ||
// http_request_latency_seconds{method="putobjectdir",le="0.01"} 0 | ||
// http_request_latency_seconds{method="putobjectdir",le="0.025"} 0 | ||
// http_request_latency_seconds{method="putobjectdir",le="0.05"} 0 | ||
// http_request_latency_seconds{method="putobjectdir",le="0.01"} 0 | ||
// http_request_latency_seconds{method="putobjectdir",le="0.25"} 0 | ||
// http_request_latency_seconds{method="putobjectdir",le="0.5"} 0 | ||
// http_request_latency_seconds{method="putobjectdir",le="1"} 1 | ||
// http_request_latency_seconds{method="putobjectdir",le="2.5"} 1 | ||
// http_request_latency_seconds{method="putobjectdir",le="5"} 1 | ||
// http_request_latency_seconds{method="putobjectdir",le="10"} 1 | ||
// http_request_latency_seconds{le="+Inf",method="putobjectdir"} 1 | ||
// http_request_latency_seconds_count{method="putobjectdir"} 1 | ||
// http_request_latency_seconds_sum{method="putobjectdir"} 0.998 | ||
}); | ||
@@ -76,0 +83,0 @@ ``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
59313
16
1418
148