diff --git a/apps/webapp/app/v3/eventRepository/common.server.ts b/apps/webapp/app/v3/eventRepository/common.server.ts index 3ba8a50c7f..2d9ca2fe2a 100644 --- a/apps/webapp/app/v3/eventRepository/common.server.ts +++ b/apps/webapp/app/v3/eventRepository/common.server.ts @@ -21,7 +21,7 @@ export function extractContextFromCarrier(carrier: Record) { } export function getNowInNanoseconds(): bigint { - return BigInt(new Date().getTime() * 1_000_000); + return convertDateToNanoseconds(new Date()); } export function getDateFromNanoseconds(nanoseconds: bigint): Date { @@ -35,7 +35,7 @@ export function calculateDurationFromStart( ) { const $endtime = typeof endTime === "string" ? new Date(endTime) : endTime; - const duration = Number(BigInt($endtime.getTime() * 1_000_000) - startTime); + const duration = Number(convertDateToNanoseconds($endtime) - startTime); if (minimumDuration && duration < minimumDuration) { return minimumDuration; diff --git a/apps/webapp/app/v3/eventRepository/index.server.ts b/apps/webapp/app/v3/eventRepository/index.server.ts index 4be392535c..c8b39a8566 100644 --- a/apps/webapp/app/v3/eventRepository/index.server.ts +++ b/apps/webapp/app/v3/eventRepository/index.server.ts @@ -7,6 +7,7 @@ import { FEATURE_FLAG } from "../featureFlags"; import { flag } from "../featureFlags.server"; import { getTaskEventStore } from "../taskEventStore.server"; import { clickhouseFactory } from "~/services/clickhouse/clickhouseFactoryInstance.server"; +import { convertDateToNanoseconds } from "./common.server"; export const EVENT_STORE_TYPES = { POSTGRES: "postgres", @@ -28,10 +29,7 @@ export type EventStoreType = (typeof EVENT_STORE_TYPES)[keyof typeof EVENT_STORE * directly and gate startup on `clickhouseFactory.isReady()`. Everything else * should use {@link getEventRepositoryForStore}, the async variant below. */ -function resolveEventRepositoryForStore( - store: string, - organizationId: string -): IEventRepository { +function resolveEventRepositoryForStore(store: string, organizationId: string): IEventRepository { if (store === EVENT_STORE_TYPES.CLICKHOUSE || store === EVENT_STORE_TYPES.CLICKHOUSE_V2) { return clickhouseFactory.getEventRepositoryForOrganizationSync(store, organizationId) .repository; @@ -262,7 +260,7 @@ async function recordRunEvent( runId: foundRun.friendlyId, ...attributes, }, - startTime: BigInt((startTime?.getTime() ?? Date.now()) * 1_000_000), + startTime: convertDateToNanoseconds(startTime ?? new Date()), ...optionsRest, }); diff --git a/apps/webapp/app/v3/runEngineHandlers.server.ts b/apps/webapp/app/v3/runEngineHandlers.server.ts index 3277d74ba6..bf0b9c95bd 100644 --- a/apps/webapp/app/v3/runEngineHandlers.server.ts +++ b/apps/webapp/app/v3/runEngineHandlers.server.ts @@ -16,7 +16,10 @@ import { MetadataTooLargeError } from "~/utils/packets"; import { QueueSizeLimitExceededError } from "~/v3/services/common.server"; import { TriggerTaskService } from "~/v3/services/triggerTask.server"; import { tracer } from "~/v3/tracer.server"; -import { createExceptionPropertiesFromError } from "./eventRepository/common.server"; +import { + convertDateToNanoseconds, + createExceptionPropertiesFromError, +} from "./eventRepository/common.server"; import { getEventRepositoryForStore, recordRunDebugLog } from "./eventRepository/index.server"; import { roomFromFriendlyRunId, socketIo } from "./handleSocketIo.server"; import { engine } from "./runEngine.server"; @@ -464,7 +467,7 @@ export function registerRunEngineEventBusHandlers() { ); await eventRepository.recordEvent(retryMessage, { - startTime: BigInt(time.getTime() * 1000000), + startTime: convertDateToNanoseconds(time), taskSlug: run.taskIdentifier, environment, attributes: { diff --git a/apps/webapp/test/eventRepositoryNanoseconds.test.ts b/apps/webapp/test/eventRepositoryNanoseconds.test.ts new file mode 100644 index 0000000000..4c50f6a4e7 --- /dev/null +++ b/apps/webapp/test/eventRepositoryNanoseconds.test.ts @@ -0,0 +1,29 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { + calculateDurationFromStart, + convertDateToNanoseconds, + getNowInNanoseconds, +} from "~/v3/eventRepository/common.server"; + +describe("event repository nanosecond conversion", () => { + afterEach(() => { + vi.useRealTimers(); + }); + + it("converts epoch milliseconds to nanoseconds after BigInt conversion", () => { + const date = new Date(1_700_000_000_001); + + expect(convertDateToNanoseconds(date)).toBe(1_700_000_000_001_000_000n); + }); + + it("uses precise nanosecond conversion for current time and durations", () => { + const startTime = convertDateToNanoseconds(new Date(1_700_000_000_001)); + const endTime = new Date(1_700_000_000_003); + + vi.useFakeTimers(); + vi.setSystemTime(endTime); + + expect(getNowInNanoseconds()).toBe(1_700_000_000_003_000_000n); + expect(calculateDurationFromStart(startTime)).toBe(2_000_000); + }); +});