diff --git a/packages/backend/src/managers/applicationManager.spec.ts b/packages/backend/src/managers/applicationManager.spec.ts index 93d177fa5..c614e3f5d 100644 --- a/packages/backend/src/managers/applicationManager.spec.ts +++ b/packages/backend/src/managers/applicationManager.spec.ts @@ -208,6 +208,7 @@ describe('pullApplication', () => { Running: true, }, }); + vi.spyOn(utils, 'getDurationSecondsSince').mockReturnValue(99); await manager.pullApplication(recipe, model); const gitCloneOptions = { repository: 'repo', @@ -222,10 +223,14 @@ describe('pullApplication', () => { } expect(doDownloadModelWrapperSpy).toHaveBeenCalledOnce(); expect(mocks.builImageMock).toHaveBeenCalledOnce(); - expect(mocks.logUsageMock).toHaveBeenNthCalledWith(1, 'model.download', { 'model.id': 'model1' }); + expect(mocks.logUsageMock).toHaveBeenNthCalledWith(1, 'model.download', { + 'model.id': 'model1', + durationSeconds: 99, + }); expect(mocks.logUsageMock).toHaveBeenNthCalledWith(2, 'recipe.pull', { 'recipe.id': 'recipe1', 'recipe.name': 'Recipe 1', + durationSeconds: 99, }); }); test('pullApplication should not clone repository if folder already exists locally', async () => { diff --git a/packages/backend/src/managers/applicationManager.ts b/packages/backend/src/managers/applicationManager.ts index 17057bd26..943b4f307 100644 --- a/packages/backend/src/managers/applicationManager.ts +++ b/packages/backend/src/managers/applicationManager.ts @@ -31,7 +31,7 @@ import type { ModelInfo } from '@shared/src/models/IModelInfo'; import type { ModelsManager } from './modelsManager'; import { getPortsInfo } from '../utils/ports'; import { goarch } from '../utils/arch'; -import { isEndpointAlive, timeout } from '../utils/utils'; +import { getDurationSecondsSince, isEndpointAlive, timeout } from '../utils/utils'; export const CONFIG_FILENAME = 'ai-studio.yaml'; @@ -70,6 +70,7 @@ export class ApplicationManager { ) {} async pullApplication(recipe: Recipe, model: ModelInfo) { + const startTime = performance.now(); // Create a TaskUtils object to help us const taskUtil = new RecipeStatusUtils(recipe.id, this.recipeStatusRegistry); @@ -101,7 +102,8 @@ export class ApplicationManager { const podInfo = await this.createApplicationPod(images, modelPath, taskUtil); await this.runApplication(podInfo, taskUtil); - this.telemetry.logUsage('recipe.pull', { 'recipe.id': recipe.id, 'recipe.name': recipe.name }); + const durationSeconds = getDurationSecondsSince(startTime); + this.telemetry.logUsage('recipe.pull', { 'recipe.id': recipe.id, 'recipe.name': recipe.name, durationSeconds }); } async runApplication(podInfo: PodInfo, taskUtil: RecipeStatusUtils) { diff --git a/packages/backend/src/managers/modelsManager.spec.ts b/packages/backend/src/managers/modelsManager.spec.ts index 01cccffa6..9bc78dc58 100644 --- a/packages/backend/src/managers/modelsManager.spec.ts +++ b/packages/backend/src/managers/modelsManager.spec.ts @@ -27,6 +27,7 @@ import type { CatalogManager } from './catalogManager'; import type { ModelInfo } from '@shared/src/models/IModelInfo'; import { RecipeStatusUtils } from '../utils/recipeStatusUtils'; import type { RecipeStatusRegistry } from '../registries/RecipeStatusRegistry'; +import * as utils from '../utils/utils'; const mocks = vi.hoisted(() => { return { @@ -356,6 +357,7 @@ describe('downloadModel', () => { .mockImplementation((_modelId: string, _url: string, _taskUtil: RecipeStatusUtils, _destFileName?: string) => { return Promise.resolve(''); }); + vi.spyOn(utils, 'getDurationSecondsSince').mockReturnValue(99); await manager.downloadModel( { id: 'id', @@ -373,7 +375,7 @@ describe('downloadModel', () => { }, state: 'loading', }); - expect(mocks.logUsageMock).toHaveBeenNthCalledWith(1, 'model.download', { 'model.id': 'id' }); + expect(mocks.logUsageMock).toHaveBeenNthCalledWith(1, 'model.download', { 'model.id': 'id', durationSeconds: 99 }); }); test('retrieve model path if already on disk', async () => { vi.spyOn(manager, 'isModelOnDisk').mockReturnValue(true); diff --git a/packages/backend/src/managers/modelsManager.ts b/packages/backend/src/managers/modelsManager.ts index 928f75258..968462c91 100644 --- a/packages/backend/src/managers/modelsManager.ts +++ b/packages/backend/src/managers/modelsManager.ts @@ -26,6 +26,7 @@ import type { CatalogManager } from './catalogManager'; import type { ModelInfo } from '@shared/src/models/IModelInfo'; import * as podmanDesktopApi from '@podman-desktop/api'; import type { RecipeStatusUtils } from '../utils/recipeStatusUtils'; +import { getDurationSecondsSince } from '../utils/utils'; export type DownloadModelResult = DownloadModelSuccessfulResult | DownloadModelFailureResult; @@ -180,8 +181,10 @@ export class ModelsManager { }); try { + const startTime = performance.now(); const result = await this.doDownloadModelWrapper(model.id, model.url, taskUtil); - this.telemetry.logUsage('model.download', { 'model.id': model.id }); + const durationSeconds = getDurationSecondsSince(startTime); + this.telemetry.logUsage('model.download', { 'model.id': model.id, durationSeconds }); return result; } catch (e) { console.error(e); diff --git a/packages/backend/src/utils/utils.ts b/packages/backend/src/utils/utils.ts index 33ffa0fe9..d59f1efc3 100644 --- a/packages/backend/src/utils/utils.ts +++ b/packages/backend/src/utils/utils.ts @@ -45,3 +45,7 @@ export async function isEndpointAlive(endPoint: string): Promise { }); }); } + +export function getDurationSecondsSince(startTimeMs: number) { + return Math.round((performance.now() - startTimeMs) / 1000); +}