Skip to content

Commit

Permalink
Brought promjs
Browse files Browse the repository at this point in the history
  • Loading branch information
Alejandro Frías Díaz committed Nov 21, 2023
1 parent a5bd77e commit 9282f99
Show file tree
Hide file tree
Showing 16 changed files with 462 additions and 12 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- `promjs` dependency brought to the same repo after it seems to be deprecated.

## [0.3.0] - 2022-09-07

### Added
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
"@shopify/network": "^3.0.0",
"@shopify/react-performance": "^4.0.0",
"events": "^3.3.0",
"promjs": "^0.4.1",
"react-beforeunload": "^2.6.0"
},
"devDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion src/MetricsContext.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Registry } from 'promjs/registry';
import { createContext, useContext } from 'react';

import { Registry } from './promjs/registry';

export interface NavigationData {
start: number;
duration: number;
Expand Down
2 changes: 1 addition & 1 deletion src/MetricsProvider.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Stage, usePerformanceMark } from '@shopify/react-performance';
import { render, screen } from '@testing-library/react';
import { Metric } from 'promjs';
import { useEffect } from 'react';
import { Mock } from 'vitest';

import { hasAnyRequest, waitForRequests } from '../test/mockServer';
import { GoldenMetrics, MetricDefinition } from './createMetrics';
import { useMetrics } from './MetricsContext';
import { MetricsProvider } from './MetricsProvider';
import { Metric } from './promjs';

const LoadMetricChecker = ({
onLoad,
Expand Down
4 changes: 2 additions & 2 deletions src/MetricsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
useNavigationListener,
} from '@shopify/react-performance';
import EventEmitter from 'events';
import prom from 'promjs';
import {
PropsWithChildren,
useCallback,
Expand All @@ -21,6 +20,7 @@ import {
MetricDefinition,
} from './createMetrics';
import { MetricsContext, NavigationData } from './MetricsContext';
import { createRegistry } from './promjs';
import { addToMetrics, sendMetricsToGateway } from './utils';

export interface MetricsProviderProps {
Expand Down Expand Up @@ -106,7 +106,7 @@ const MetricsProvider = ({
null,
);

const registry = useRef(prom());
const registry = useRef(createRegistry());
const eventEmitter = useRef(new EventEmitter());

const defaultTags = useMemo(
Expand Down
2 changes: 1 addition & 1 deletion src/createMetrics.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Registry } from 'promjs/registry';
import { Registry } from './promjs/registry';

export enum GoldenMetrics {
AppLoaded = 'prom_react_app_loaded',
Expand Down
57 changes: 57 additions & 0 deletions src/promjs/collector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Labels, Metric, MetricValue } from './types';
import { findExistingMetric } from './utils';

export abstract class Collector<Value extends MetricValue> {
private readonly data: Metric<Value>[];

constructor() {
this.data = [];
}

get(labels?: Labels): Metric<Value> | undefined {
return findExistingMetric<Value>(labels, this.data);
}

set(value: Value, labels?: Labels): this {
const existing = findExistingMetric(labels, this.data);
if (existing) {
existing.value = value;
} else {
this.data.push({
labels,
value,
});
}

return this;
}

collect(labels?: Labels): Metric<Value>[] {
if (!labels) {
return this.data;
}
return this.data.filter((item) => {
if (!item.labels) {
return false;
}
const entries = Object.entries(labels);
for (let i = 0; i < entries.length; i += 1) {
const [label, value] = entries[i];
if (item.labels[label] !== value) {
return false;
}
}
return true;
});
}

resetAll(): this {
for (let i = 0; i < this.data.length; i += 1) {
this.reset(this.data[i].labels);
}

return this;
}

abstract reset(labels?: Labels): void;
}
25 changes: 25 additions & 0 deletions src/promjs/counter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Collector } from './collector';
import { CounterValue, Labels } from './types';

export class Counter extends Collector<CounterValue> {
inc(labels?: Labels): this {
this.add(1, labels);
return this;
}

add(amount: number, labels?: Labels): this {
if (amount < 0) {
throw new Error(
`Expected increment amount to be greater than -1. Received: ${amount}`,
);
}
const metric = this.get(labels);
this.set(metric ? metric.value + amount : amount, labels);

return this;
}

reset(labels?: Labels): void {
this.set(0, labels);
}
}
16 changes: 16 additions & 0 deletions src/promjs/gauge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Counter } from './counter';
import { Labels } from './types';

export class Gauge extends Counter {
dec(labels?: Labels): this {
const metric = this.get(labels);
this.set(metric ? metric.value - 1 : 0, labels);
return this;
}

sub(amount: number, labels?: Labels): this {
const metric = this.get(labels);
this.set(metric ? metric.value - amount : 0, labels);
return this;
}
}
74 changes: 74 additions & 0 deletions src/promjs/histogram.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Collector } from './collector';
import { HistogramValue, HistogramValueEntries, Labels } from './types';

function findMinBucketIndex(ary: number[], num: number): number | undefined {
if (num < ary[ary.length - 1]) {
for (let i = 0; i < ary.length; i += 1) {
if (num <= ary[i]) {
return i;
}
}
}

return undefined;
}

function getInitialValue(buckets: number[]): HistogramValue {
// Make the skeleton to which values will be saved.
const entries = buckets.reduce(
(result, b) => {
// eslint-disable-next-line no-param-reassign
result[b.toString()] = 0;
return result;
},
{ '+Inf': 0 } as HistogramValueEntries,
);

return {
entries,
sum: 0,
count: 0,
raw: [],
};
}

export class Histogram extends Collector<HistogramValue> {
private readonly buckets: number[];

constructor(buckets: number[] = []) {
super();
// Sort to get smallest -> largest in order.
this.buckets = buckets.sort((a, b) => (a > b ? 1 : -1));
this.set(getInitialValue(this.buckets));
this.observe = this.observe.bind(this);
}

observe(value: number, labels?: Labels): this {
let metric = this.get(labels);
if (metric == null) {
// Create a metric for the labels.
metric = this.set(getInitialValue(this.buckets), labels).get(labels)!;
}

metric.value.raw.push(value);
metric.value.entries['+Inf'] += 1;

const minBucketIndex = findMinBucketIndex(this.buckets, value);

if (minBucketIndex != null) {
for (let i = minBucketIndex; i < this.buckets.length; i += 1) {
const val = metric.value.entries[this.buckets[i].toString()];
metric.value.entries[this.buckets[i].toString()] = val + 1;
}
}

metric.value.sum = metric.value.raw.reduce((sum, v) => sum + v, 0);
metric.value.count += 1;

return this;
}

reset(labels?: Labels): void {
this.set(getInitialValue(this.buckets), labels);
}
}
5 changes: 5 additions & 0 deletions src/promjs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Registry } from './registry';

export * from './types';

export const createRegistry = () => new Registry();
Loading

0 comments on commit 9282f99

Please sign in to comment.