From 4dd2eb9349eb16006d1ebba99c848b8f6c51baf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20S=C3=B3jko?= Date: Thu, 4 Jan 2024 15:51:06 +0100 Subject: [PATCH] feat(syncing-server): store per user content size utilization and item operations metrics --- .../syncing-server/src/Domain/Metrics/Metric.ts | 2 ++ .../src/Domain/Metrics/MetricsStoreInterface.ts | 1 + .../Syncing/SaveNewItem/SaveNewItem.spec.ts | 1 + .../UseCase/Syncing/SaveNewItem/SaveNewItem.ts | 10 ++++++++++ .../UpdateExistingItem.spec.ts | 1 + .../UpdateExistingItem/UpdateExistingItem.ts | 9 +++++++++ .../src/Infra/Dummy/DummyMetricStore.ts | 4 ++++ .../src/Infra/Redis/RedisMetricStore.ts | 17 +++++++++++++++++ 8 files changed, 45 insertions(+) diff --git a/packages/syncing-server/src/Domain/Metrics/Metric.ts b/packages/syncing-server/src/Domain/Metrics/Metric.ts index d15474d65..293d63df1 100644 --- a/packages/syncing-server/src/Domain/Metrics/Metric.ts +++ b/packages/syncing-server/src/Domain/Metrics/Metric.ts @@ -6,6 +6,8 @@ export class Metric extends ValueObject { static readonly NAMES = { ItemCreated: 'ItemCreated', ItemUpdated: 'ItemUpdated', + ContentSizeUtilized: 'ContentSizeUtilized', + ItemOperation: 'ItemOperation', } static create(props: MetricProps): Result { diff --git a/packages/syncing-server/src/Domain/Metrics/MetricsStoreInterface.ts b/packages/syncing-server/src/Domain/Metrics/MetricsStoreInterface.ts index 244f61bfb..8f26068a2 100644 --- a/packages/syncing-server/src/Domain/Metrics/MetricsStoreInterface.ts +++ b/packages/syncing-server/src/Domain/Metrics/MetricsStoreInterface.ts @@ -1,6 +1,7 @@ import { Metric } from './Metric' export interface MetricsStoreInterface { + storeUserBasedMetric(metric: Metric, value: number, userUuid: string): Promise storeMetric(metric: Metric): Promise getStatistics( name: string, diff --git a/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.spec.ts b/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.spec.ts index d11b4c1bf..f9214c4ca 100644 --- a/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.spec.ts +++ b/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.spec.ts @@ -27,6 +27,7 @@ describe('SaveNewItem', () => { metricsStore = {} as jest.Mocked metricsStore.storeMetric = jest.fn() + metricsStore.storeUserBasedMetric = jest.fn() item1 = Item.create( { diff --git a/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.ts b/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.ts index bc09ea56c..a6553deb1 100644 --- a/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.ts +++ b/packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.ts @@ -110,6 +110,7 @@ export class SaveNewItem implements UseCaseInterface { return Result.fail(itemOrError.getError()) } const newItem = itemOrError.getValue() + newItem.props.contentSize = Buffer.byteLength(JSON.stringify(newItem)) if (dto.itemHash.representsASharedVaultItem()) { const sharedVaultAssociationOrError = SharedVaultAssociation.create({ @@ -138,6 +139,15 @@ export class SaveNewItem implements UseCaseInterface { await this.itemRepository.insert(newItem) + await this.metricsStore.storeUserBasedMetric( + Metric.create({ + name: Metric.NAMES.ContentSizeUtilized, + timestamp: this.timer.getTimestampInMicroseconds(), + }).getValue(), + newItem.props.contentSize, + userUuid.value, + ) + await this.metricsStore.storeMetric( Metric.create({ name: Metric.NAMES.ItemCreated, timestamp: this.timer.getTimestampInMicroseconds() }).getValue(), ) diff --git a/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts b/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts index e0122074e..9b288cda3 100644 --- a/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts +++ b/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts @@ -53,6 +53,7 @@ describe('UpdateExistingItem', () => { metricsStore = {} as jest.Mocked metricsStore.storeMetric = jest.fn() + metricsStore.storeUserBasedMetric = jest.fn() item1 = Item.create( { diff --git a/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts b/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts index f1e470e8d..a2cfa535f 100644 --- a/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts +++ b/packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts @@ -180,6 +180,15 @@ export class UpdateExistingItem implements UseCaseInterface { Metric.create({ name: Metric.NAMES.ItemUpdated, timestamp: this.timer.getTimestampInMicroseconds() }).getValue(), ) + await this.metricsStore.storeUserBasedMetric( + Metric.create({ + name: Metric.NAMES.ContentSizeUtilized, + timestamp: this.timer.getTimestampInMicroseconds(), + }).getValue(), + dto.existingItem.props.contentSize, + userUuid.value, + ) + /* istanbul ignore next */ const revisionsFrequency = dto.isFreeUser ? this.freeRevisionFrequency : this.premiumRevisionFrequency diff --git a/packages/syncing-server/src/Infra/Dummy/DummyMetricStore.ts b/packages/syncing-server/src/Infra/Dummy/DummyMetricStore.ts index 531a42a8b..37cabdd7b 100644 --- a/packages/syncing-server/src/Infra/Dummy/DummyMetricStore.ts +++ b/packages/syncing-server/src/Infra/Dummy/DummyMetricStore.ts @@ -2,6 +2,10 @@ import { MetricsStoreInterface } from '../../Domain/Metrics/MetricsStoreInterfac import { Metric } from '../../Domain/Metrics/Metric' export class DummyMetricStore implements MetricsStoreInterface { + async storeUserBasedMetric(_metric: Metric, _value: number, _userUuid: string): Promise { + // do nothing + } + async storeMetric(_metric: Metric): Promise { // do nothing } diff --git a/packages/syncing-server/src/Infra/Redis/RedisMetricStore.ts b/packages/syncing-server/src/Infra/Redis/RedisMetricStore.ts index ed40feb1f..005deb258 100644 --- a/packages/syncing-server/src/Infra/Redis/RedisMetricStore.ts +++ b/packages/syncing-server/src/Infra/Redis/RedisMetricStore.ts @@ -6,12 +6,29 @@ import { Metric } from '../../Domain/Metrics/Metric' export class RedisMetricStore implements MetricsStoreInterface { private readonly METRIC_PREFIX = 'metric' + private readonly METRIC_PER_USER_PREFIX = 'metric-user' constructor( private redisClient: IORedis.Redis, private timer: TimerInterface, ) {} + async storeUserBasedMetric(metric: Metric, value: number, userUuid: string): Promise { + const date = this.timer.convertMicrosecondsToDate(metric.props.timestamp) + const dateToTheMinuteString = this.timer.convertDateToFormattedString(date, 'YYYY-MM-DD HH:mm') + const key = `${this.METRIC_PER_USER_PREFIX}:${userUuid}:${metric.props.name}:${dateToTheMinuteString}` + + const pipeline = this.redisClient.pipeline() + + pipeline.incrbyfloat(key, value) + pipeline.incr(`${this.METRIC_PER_USER_PREFIX}:${userUuid}:${Metric.NAMES.ItemOperation}:${dateToTheMinuteString}`) + + const expirationTime = 60 * 60 * 24 + pipeline.expire(key, expirationTime) + + await pipeline.exec() + } + async getStatistics( name: string, from: number,