@atomist/automation-client
Version:
Atomist API for software low-level client
141 lines (116 loc) • 4.41 kB
text/typescript
import { LRUMap } from "lru_map";
import { EventStore } from "../../spi/event/EventStore";
import {
CommandIncoming,
EventIncoming,
} from "../transport/RequestProcessor";
import {
guid,
hideString,
} from "../util/string";
/**
* Simple {EventStore} implementation that stores events in memory.
*/
export class InMemoryEventStore implements EventStore {
private readonly eventCache: LRUMap<CacheKey, EventIncoming>;
private readonly commandCache: LRUMap<CacheKey, CommandIncoming>;
private readonly messageCache: LRUMap<CacheKey, any>;
// 5 mins for 3 hours
private readonly eventSer = new RRD(60 * 5, 12 * 3);
private readonly commandSer = new RRD(60 * 5, 12 * 3);
constructor() {
this.eventCache = new LRUMap<CacheKey, EventIncoming>(100);
this.commandCache = new LRUMap<CacheKey, CommandIncoming>(100);
this.messageCache = new LRUMap<CacheKey, any>(100);
}
public recordEvent(event: EventIncoming) {
const id = event.extensions.correlation_id ? event.extensions.correlation_id : guid();
this.eventCache.set({ guid: id, correlationId: id, ts: new Date().getTime() }, event);
this.eventSer.update(1);
return id;
}
public recordCommand(command: CommandIncoming) {
const id = command.correlation_id ? command.correlation_id : guid();
this.commandCache.set({ guid: id, correlationId: id, ts: new Date().getTime() }, command);
this.commandSer.update(1);
return id;
}
public recordMessage(id: string, correlationId: string, message: any) {
this.messageCache.set({ guid: id, correlationId, ts: new Date().getTime() }, message);
return id;
}
public events(from: number = -1): any[] {
const entries: any[] = [];
this.eventCache.forEach((v, k) => k.ts > from ? entries.push({ key: k, value: hideSecrets(v) }) : null);
return entries;
}
public eventSeries(): [number[], number[]] {
const buckets = this.eventSer.fetch().filter(b => b.ts);
return [buckets.map(b => b.value), buckets.map(b => b.ts)];
}
public commands(from: number = -1): any[] {
const entries: any[] = [];
this.commandCache.forEach((v, k) => k.ts > from ? entries.push({ key: k, value: hideSecrets(v) }) : null);
return entries;
}
public commandSeries(): [number[], number[]] {
const buckets = this.commandSer.fetch().filter(b => b.ts);
return [buckets.map(b => b.value), buckets.map(b => b.ts)];
}
public messages(from: number = -1): any[] {
const entries: any[] = [];
this.messageCache.forEach((v, k) => k.ts > from ? entries.push({ key: k, value: v }) : null);
return entries;
}
}
function hideSecrets(event: EventIncoming | CommandIncoming) {
event.secrets = event.secrets
? event.secrets.map(s => ({ uri: s.uri, value: hideString(s.value) })) : undefined;
return event;
}
interface CacheKey {
guid: string;
correlationId: string;
ts: number;
}
class Count {
private value: number = 0;
public update(data: number): number {
this.value++;
return this.value;
}
public result() {
const value = this.value;
this.value = 0;
return value;
}
}
class RRD {
private readonly buckets: any[];
private readonly interval: any;
private index: number;
private readonly iid: any;
private readonly dataFunc = new Count();
constructor(interval, count) {
this.buckets = new Array(count).fill(0);
this.buckets[0] = { ts: Math.floor(Date.now() / 1000), value: 0 };
this.index = 1;
this.interval = interval * 1000;
this.iid = setInterval(this.increment.bind(this), this.interval);
}
public increment() {
if (this.index < this.buckets.length) {
this.buckets[this.index] = { ts: Math.floor(Date.now() / 1000), value: this.dataFunc.result() };
this.index += 1;
} else {
this.buckets.push({ ts: Math.floor(Date.now() / 1000), value: this.dataFunc.result() });
this.buckets.shift();
}
}
public update(data: any) {
this.buckets[this.index] = { ts: Math.floor(Date.now() / 1000), value: this.dataFunc.update(data) };
}
public fetch(): any[] {
return this.buckets;
}
}