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

This commit is contained in:
Karol Sójko 2024-01-04 15:51:06 +01:00
parent 709aec5eeb
commit 4dd2eb9349
No known key found for this signature in database
GPG key ID: C2F813669419D05F
8 changed files with 45 additions and 0 deletions

View file

@ -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> {

View file

@ -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,

View file

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

View file

@ -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(),
)

View file

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

View file

@ -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

View file

@ -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
}

View file

@ -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,