소스 검색

feat(syncing-server): store per user content size utilization and item operations metrics

Karol Sójko 1 년 전
부모
커밋
4dd2eb9349

+ 2 - 0
packages/syncing-server/src/Domain/Metrics/Metric.ts

@@ -6,6 +6,8 @@ export class Metric extends ValueObject<MetricProps> {
   static readonly NAMES = {
     ItemCreated: 'ItemCreated',
     ItemUpdated: 'ItemUpdated',
+    ContentSizeUtilized: 'ContentSizeUtilized',
+    ItemOperation: 'ItemOperation',
   }
 
   static create(props: MetricProps): Result<Metric> {

+ 1 - 0
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<void>
   storeMetric(metric: Metric): Promise<void>
   getStatistics(
     name: string,

+ 1 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.spec.ts

@@ -27,6 +27,7 @@ describe('SaveNewItem', () => {
 
     metricsStore = {} as jest.Mocked<MetricsStoreInterface>
     metricsStore.storeMetric = jest.fn()
+    metricsStore.storeUserBasedMetric = jest.fn()
 
     item1 = Item.create(
       {

+ 10 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.ts

@@ -110,6 +110,7 @@ export class SaveNewItem implements UseCaseInterface<Item> {
       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<Item> {
 
     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(),
     )

+ 1 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts

@@ -53,6 +53,7 @@ describe('UpdateExistingItem', () => {
 
     metricsStore = {} as jest.Mocked<MetricsStoreInterface>
     metricsStore.storeMetric = jest.fn()
+    metricsStore.storeUserBasedMetric = jest.fn()
 
     item1 = Item.create(
       {

+ 9 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts

@@ -180,6 +180,15 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
       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
 

+ 4 - 0
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<void> {
+    // do nothing
+  }
+
   async storeMetric(_metric: Metric): Promise<void> {
     // do nothing
   }

+ 17 - 0
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<void> {
+    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,