Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,17 @@ help:
@echo 'Usage:'
@sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'

## test: run tests with coverage enabled
## build: run build
.PHONY: build
build:
npm run build

## test: run build-and-test
.PHONY: test
test:
npm run build-and-test

## coverage: run test-with-coverage
.PHONY: coverage
coverage:
npm run test-with-coverage
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nflx-spectator",
"version": "3.0.13",
"version": "3.0.14",
"license": "Apache-2.0",
"homepage": "https://github.com/Netflix/spectator-js",
"author": "Netflix Telemetry Engineering <[email protected]>",
Expand Down
4 changes: 3 additions & 1 deletion src/common_tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export function validate_tags(tags: Record<string, string>): Record<string, stri

for (const key in tags) {
const val = tags[key];
if (key.length == 0 || val.length == 0) continue;
// javascript protection check
if (typeof key !== "string" || typeof val !== "string") continue;
if (key.length < 2 || key.length > 60 || val.length < 1 || val.length > 120) continue;
valid_tags[key] = val;
}

Expand Down
2 changes: 1 addition & 1 deletion src/logger/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function get_logger(level_name?: string): Logger {
}

const logger: Logger = {
is_level_enabled: (name: string) => levels[name] >= level_filter,
is_level_enabled: (name: string): boolean => levels[name] >= level_filter,
trace: function (): void {
throw new Error("Function not implemented.");
},
Expand Down
4 changes: 4 additions & 0 deletions src/meter/gauge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ export class Gauge extends Meter {
const line = `${this._meter_type_symbol}:${this._id.spectatord_id}:${value}`
return this._writer.write(line);
}

update(value: number): Promise<void> {
return this.set(value);
}
}
25 changes: 20 additions & 5 deletions src/meter/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import {validate_tags} from "../common_tags.js";

export type Tags = Record<string, string>;

export function tags_toString(tags: Tags): string {
export function tags_toString(tags: Tags | undefined): string {
if (!tags) {
return "{}"
}

let result: string = "{";

if (Object.entries(tags).length > 0) {
Expand All @@ -28,6 +32,8 @@ export class Id {
private readonly _logger: Logger;
private readonly _name: string;
private readonly _tags: Tags;

public invalid: boolean = false;
public spectatord_id: string;

constructor(name: string, tags?: Tags, logger?: Logger) {
Expand All @@ -43,18 +49,19 @@ export class Id {
if (tags == undefined) {
this._tags = {}
} else {
this._tags = this.validate_tags(tags);
this._tags = this.validate_tags_for_id(tags);
}

this.spectatord_id = this.to_spectatord_id(this._name, this._tags);
}

private validate_tags(tags: Tags): Tags {
private validate_tags_for_id(tags: Tags): Tags {
const valid_tags: Record<string, string> = validate_tags(tags);

if (Object.entries(tags).length != Object.entries(valid_tags).length) {
this._logger.warn(`Id(name=${this._name}, tags=${tags_toString(tags)}) is invalid due to tag keys or values which are ` +
`zero-length strings; proceeding with truncated tags Id(name=${this._name}, tags=${tags_toString(valid_tags)})`);
this._logger.warn(`Id(name=${this._name}, tags=${tags_toString(tags)}) is invalid due to tag keys or ` +
`values which are too short (k < 2, v < 1), or too long (k > 60, v > 120); proceeding with truncated ` +
`tags Id(name=${this._name}, tags=${tags_toString(valid_tags)})`);
}

return valid_tags;
Expand All @@ -65,6 +72,14 @@ export class Id {
}

private to_spectatord_id(name: string, tags?: Tags): string {
// javascript and length protection check
if (typeof name !== "string" || name.length < 2 || name.length > 255) {
this._logger.warn(`Id(name=${name}, tags=${tags_toString(tags)}) is invalid, because the name is not a string, ` +
`it is too short (< 2), or it is too long (> 255); metric will not be reported`);
this.invalid = true;
return '';
}

if (tags == undefined) {
tags = {};
}
Expand Down
4 changes: 4 additions & 0 deletions src/meter/max_gauge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ export class MaxGauge extends Meter {
const line = `${this._meter_type_symbol}:${this._id.spectatord_id}:${value}`
return this._writer.write(line);
}

update(value: number): Promise<void> {
return this.set(value);
}
}
8 changes: 6 additions & 2 deletions src/meter/percentile_timer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,20 @@ export class PercentileTimer extends Meter {
* start = process.hrtime();
* // do work
* registry.pct_timer("eventLatency").record(process.hrtime(start));
*
* @param {number} nanos Optional nanoseconds, as if recording values extracted from process.hrtime(). This
* variation should be paired with the first parameter representing seconds.
*/
record(seconds: number | number[] | bigint): Promise<void> {
record(seconds: number | number[] | bigint, nanos?: number): Promise<void> {
let elapsed: number;

if (typeof seconds === 'bigint') {
elapsed = Number(seconds) / 1e9;
} else if (seconds instanceof Array) {
elapsed = seconds[0] + (seconds[1] / 1e9);
} else {
elapsed = seconds;
nanos = nanos || 0;
elapsed = seconds + (nanos / 1e9);
}

if (elapsed >= 0) {
Expand Down
9 changes: 7 additions & 2 deletions src/meter/timer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export class Timer extends Meter {

/**
* @param {number|number[]|bigint} seconds
*
* Number of seconds, which may be:
*
* - Integer or fractional seconds.
Expand All @@ -23,16 +24,20 @@ export class Timer extends Meter {
* start = process.hrtime();
* // do work
* registry.timer("eventLatency").record(process.hrtime(start));
*
* @param {number} nanos Optional nanoseconds, as if recording values extracted from process.hrtime(). This
* variation should be paired with the first parameter representing seconds.
*/
record(seconds: number | number[] | bigint): Promise<void> {
record(seconds: number | number[] | bigint, nanos?: number): Promise<void> {
let elapsed: number;

if (typeof seconds === 'bigint') {
elapsed = Number(seconds) / 1e9;
} else if (seconds instanceof Array) {
elapsed = seconds[0] + (seconds[1] / 1e9);
} else {
elapsed = seconds;
nanos = nanos || 0;
elapsed = seconds + (nanos / 1e9);
}

if (elapsed >= 0) {
Expand Down
61 changes: 51 additions & 10 deletions src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {PercentileDistributionSummary} from "./meter/percentile_dist_summary.js"
import {PercentileTimer} from "./meter/percentile_timer.js";
import {Timer} from "./meter/timer.js";
import {new_writer, WriterUnion} from "./writer/new_writer.js";
import {NoopWriter} from "./writer/noop_writer.js";


export class Registry {
Expand Down Expand Up @@ -69,79 +70,119 @@ export class Registry {
}

age_gauge(name: string, tags: Tags = {}): AgeGauge {
return new AgeGauge(this.new_id(name, tags), this._writer);
const id = this.new_id(name, tags);
if (id.invalid) {
return new AgeGauge(id, new NoopWriter());
}
return new AgeGauge(id, this._writer);
}

age_gauge_with_id(id: Id): AgeGauge {
return new AgeGauge(id, this._writer);
}

counter(name: string, tags: Tags = {}): Counter {
return new Counter(this.new_id(name, tags), this._writer);
const id = this.new_id(name, tags);
if (id.invalid) {
return new Counter(id, new NoopWriter());
}
return new Counter(id, this._writer);
}

counter_with_id(id: Id): Counter {
return new Counter(id, this._writer);
}

distribution_summary(name: string, tags: Tags = {}): DistributionSummary {
return new DistributionSummary(this.new_id(name, tags), this._writer);
const id = this.new_id(name, tags);
if (id.invalid) {
return new DistributionSummary(id, new NoopWriter());
}
return new DistributionSummary(id, this._writer);
}

distribution_summary_with_id(id: Id): DistributionSummary {
return new DistributionSummary(id, this._writer);
}

gauge(name: string, tags: Tags = {}, ttl_seconds?: number): Gauge {
return new Gauge(this.new_id(name, tags), this._writer, ttl_seconds);
const id = this.new_id(name, tags);
if (id.invalid) {
return new Gauge(id, new NoopWriter(), ttl_seconds);
}
return new Gauge(id, this._writer, ttl_seconds);
}

gauge_with_id(id: Id, ttl_seconds?: number): Gauge {
return new Gauge(id, this._writer, ttl_seconds);
}

max_gauge(name: string, tags: Tags = {}): MaxGauge {
return new MaxGauge(this.new_id(name, tags), this._writer);
const id = this.new_id(name, tags);
if (id.invalid) {
return new MaxGauge(id, new NoopWriter());
}
return new MaxGauge(id, this._writer);
}

max_gauge_with_id(id: Id): MaxGauge {
return new MaxGauge(id, this._writer);
}

monotonic_counter(name: string, tags: Tags = {}): MonotonicCounter {
return new MonotonicCounter(this.new_id(name, tags), this._writer);
const id = this.new_id(name, tags);
if (id.invalid) {
return new MonotonicCounter(id, new NoopWriter());
}
return new MonotonicCounter(id, this._writer);
}

monotonic_counter_with_id(id: Id): MonotonicCounter {
return new MonotonicCounter(id, this._writer);
}

monotonic_counter_uint(name: string, tags: Tags = {}): MonotonicCounterUint {
return new MonotonicCounterUint(this.new_id(name, tags), this._writer);
const id = this.new_id(name, tags);
if (id.invalid) {
return new MonotonicCounterUint(id, new NoopWriter());
}
return new MonotonicCounterUint(id, this._writer);
}

monotonic_counter_uint_with_id(id: Id): MonotonicCounterUint {
return new MonotonicCounterUint(id, this._writer);
}

pct_distribution_summary(name: string, tags: Tags = {}): PercentileDistributionSummary {
return new PercentileDistributionSummary(this.new_id(name, tags), this._writer);
const id = this.new_id(name, tags);
if (id.invalid) {
return new PercentileDistributionSummary(id, new NoopWriter());
}
return new PercentileDistributionSummary(id, this._writer);
}

pct_distribution_summary_with_id(id: Id): PercentileDistributionSummary {
return new PercentileDistributionSummary(id, this._writer);
}

pct_timer(name: string, tags: Tags = {}): PercentileTimer {
return new PercentileTimer(this.new_id(name, tags), this._writer);
const id = this.new_id(name, tags);
if (id.invalid) {
return new PercentileTimer(id, new NoopWriter());
}
return new PercentileTimer(id, this._writer);
}

pct_timer_with_id(id: Id): PercentileTimer {
return new PercentileTimer(id, this._writer);
}

timer(name: string, tags: Tags = {}): Timer {
return new Timer(this.new_id(name, tags), this._writer);
const id = this.new_id(name, tags);
if (id.invalid) {
return new Timer(id, new NoopWriter());
}
return new Timer(id, this._writer);
}

timer_with_id(id: Id): Timer {
Expand Down
4 changes: 2 additions & 2 deletions test/common_tags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe("Common Tags Tests", (): void => {
});

it("validate tags skips zero length strings", (): void => {
const tags = {"a": "", "": "b", "c": "1"};
assert.deepEqual({"c": "1"}, validate_tags(tags));
const tags = {"a": "", "": "b", "cc": "1"};
assert.deepEqual({"cc": "1"}, validate_tags(tags));
});
});
9 changes: 9 additions & 0 deletions test/meter/gauge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ describe("Gauge Tests", (): void => {
assert.equal("g:gauge:1", writer.last_line());
});

it("update delegates to set", (): void => {
const g = new Gauge(tid, new MemoryWriter());
const writer = g.writer() as MemoryWriter;
assert.isTrue(writer.is_empty());

g.update(1);
assert.equal("g:gauge:1", writer.last_line());
});

it("ttl_seconds", (): void => {
const g = new Gauge(tid, new MemoryWriter(), 120);
const writer = g.writer() as MemoryWriter;
Expand Down
Loading