Skip to content

Commit

Permalink
adding in stats to keyv (#1001)
Browse files Browse the repository at this point in the history
* adding in stats to keyv

* adding in stats option

* getting errors to work

* moving to counts as properties

* adding in stats
  • Loading branch information
jaredwray authored Feb 20, 2024
1 parent c831792 commit c05f0e9
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 0 deletions.
21 changes: 21 additions & 0 deletions packages/keyv/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import JSONB from 'json-buffer';
import HooksManager from './hooks-manager';
import EventManager from './event-manager';
import StatsManager from './stats-manager';

export type DeserializedData<Value> = {
value?: Value;
Expand Down Expand Up @@ -71,6 +72,8 @@ export interface Options {
compression?: CompressionAdapter;
/** Specify an adapter to use. e.g `'redis'` or `'mongodb'`. */
adapter?: 'redis' | 'mongodb' | 'mongo' | 'sqlite' | 'postgresql' | 'postgres' | 'mysql';
/** Enable or disable statistics (default is false) */
stats?: boolean;
}

type IteratorFunction = (arg: any) => AsyncGenerator<any, void>;
Expand Down Expand Up @@ -115,6 +118,7 @@ class Keyv extends EventManager {
opts: Options;
iterator?: IteratorFunction;
hooks = new HooksManager();
stats = new StatsManager(false);
constructor(uri?: string | Omit<Options, 'store'>, options_?: Omit<Options, 'store'>) {
super();
options_ = options_ ?? {};
Expand Down Expand Up @@ -157,6 +161,10 @@ class Keyv extends EventManager {
this.iterator = this.generateIterator(this.opts.store.iterator.bind(this.opts.store));
}
}

if (this.opts.stats) {
this.stats.enabled = this.opts.stats;
}
}

generateIterator(iterator: IteratorFunction): IteratorFunction {
Expand Down Expand Up @@ -234,6 +242,10 @@ class Keyv extends EventManager {
const deserializedRows = await Promise.allSettled(promises);
const result = deserializedRows.map(row => (row as PromiseFulfilledResult<any>).value);
this.hooks.trigger(KeyvHooks.POST_GET_MANY, result);
if (result.length > 0) {
this.stats.hit();
}

return result;
}

Expand Down Expand Up @@ -263,6 +275,10 @@ class Keyv extends EventManager {
}

this.hooks.trigger(KeyvHooks.POST_GET_MANY, result);
if (result.length > 0) {
this.stats.hit();
}

return result as (Array<StoredDataNoRaw<Value>> | Array<StoredDataRaw<Value>>);
}

Expand All @@ -271,15 +287,18 @@ class Keyv extends EventManager {
const deserializedData = (typeof rawData === 'string' || this.opts.compression) ? await this.opts.deserialize!<Value>(rawData as string) : rawData;

if (deserializedData === undefined || deserializedData === null) {
this.stats.miss();
return undefined;
}

if (isDataExpired(deserializedData as DeserializedData<Value>)) {
await this.delete(key);
this.stats.miss();
return undefined;
}

this.hooks.trigger(KeyvHooks.POST_GET, {key: keyPrefixed, value: deserializedData});
this.stats.hit();
return (options && options.raw) ? deserializedData : (deserializedData as DeserializedData<Value>).value;
}

Expand Down Expand Up @@ -307,6 +326,7 @@ class Keyv extends EventManager {
value = await this.opts.serialize!(value);
await store.set(keyPrefixed, value, ttl);
this.hooks.trigger(KeyvHooks.POST_SET, {key: keyPrefixed, value, ttl});
this.stats.set();
return true;
}

Expand All @@ -330,6 +350,7 @@ class Keyv extends EventManager {
const keyPrefixed = this._getKeyPrefix(key);
const result = store.delete(keyPrefixed);
this.hooks.trigger(KeyvHooks.POST_DELETE, result);
this.stats.delete();
return result;
}

Expand Down
55 changes: 55 additions & 0 deletions packages/keyv/src/stats-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import EventManager from './event-manager';

class StatsManager extends EventManager {
public enabled = true;

public hits = 0;
public misses = 0;
public sets = 0;
public deletes = 0;
public errors = 0;

constructor(enabled?: boolean) {
super();
if (enabled !== undefined) {
this.enabled = enabled;
}

this.reset();
}

hit() {
if (this.enabled) {
this.hits++;
}
}

miss() {
if (this.enabled) {
this.misses++;
}
}

set() {
if (this.enabled) {
this.sets++;
}
}

delete() {
if (this.enabled) {
this.deletes++;
}
}

reset() {
this.hits = 0;
this.misses = 0;
this.sets = 0;
this.deletes = 0;
this.errors = 0;
}
}

export default StatsManager;
module.exports = StatsManager;
54 changes: 54 additions & 0 deletions packages/keyv/test/stats-manager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import test from 'ava';
import KeyvStatsManager from '../src/stats-manager';

test('will initialize with correct stats at zero', t => {
const stats = new KeyvStatsManager();
t.is(stats.hits, 0);
});

test('will increment hits', t => {
const stats = new KeyvStatsManager();
stats.hit();
t.is(stats.hits, 1);
});

test('will increment misses', t => {
const stats = new KeyvStatsManager();
stats.miss();
t.is(stats.misses, 1);
});

test('will increment sets', t => {
const stats = new KeyvStatsManager();
stats.set();
t.is(stats.sets, 1);
});

test('will increment deletes', t => {
const stats = new KeyvStatsManager();
stats.delete();
t.is(stats.deletes, 1);
});

test('will reset stats', t => {
const stats = new KeyvStatsManager();
stats.hit();
stats.miss();
stats.set();
stats.delete();
t.is(stats.hits, 1);
t.is(stats.misses, 1);
t.is(stats.sets, 1);
t.is(stats.deletes, 1);
stats.reset();
t.is(stats.hits, 0);
t.is(stats.misses, 0);
t.is(stats.sets, 0);
t.is(stats.deletes, 0);
});

test('will not increment hits if disabled', t => {
const stats = new KeyvStatsManager(false);
stats.hit();
t.is(stats.hits, 0);
});
17 changes: 17 additions & 0 deletions packages/keyv/test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -608,3 +608,20 @@ test.serial('Keyv has should return true or false on Map', async t => {
await snooze(1100);
t.is(await keyv.has('foo'), false);
});

test.serial('Keyv opts.stats should set the stats manager', t => {
const keyv = new Keyv({stats: true});
t.is(keyv.stats.enabled, true);
});

test.serial('Keyv stats enabled should create counts', async t => {
const keyv = new Keyv({stats: true});
await keyv.set('foo', 'bar');
await keyv.get('foo');
await keyv.get('foo1');
await keyv.delete('foo');
t.is(keyv.stats.hits, 1);
t.is(keyv.stats.misses, 1);
t.is(keyv.stats.deletes, 1);
t.is(keyv.stats.sets, 1);
});

0 comments on commit c05f0e9

Please sign in to comment.