mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 10:06:21 +00:00
fix(engine): realistic worker memory budget + sizing/feedback telemetry
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildTelemetryJoinKeys } from "./feedback.js";
|
||||
|
||||
describe("buildTelemetryJoinKeys", () => {
|
||||
it("emits fid + tid and omits renders when the ring is empty", () => {
|
||||
const keys = buildTelemetryJoinKeys({
|
||||
feedbackId: "feedback-uuid",
|
||||
anonymousId: "install-uuid",
|
||||
});
|
||||
expect(keys).toBe("fid=feedback-uuid tid=install-uuid");
|
||||
});
|
||||
|
||||
it("appends recent render ids newest-last with a ! marking failed renders", () => {
|
||||
const keys = buildTelemetryJoinKeys({
|
||||
feedbackId: "f",
|
||||
anonymousId: "t",
|
||||
recentRenders: [
|
||||
{ id: "render-a", at: "2026-07-21T00:00:00Z", ok: true },
|
||||
{ id: "render-b", at: "2026-07-21T01:00:00Z", ok: false },
|
||||
],
|
||||
});
|
||||
expect(keys).toBe("fid=f tid=t renders=render-a,render-b!");
|
||||
});
|
||||
|
||||
it("stays within the backend env cap for a full ring of uuid render ids", () => {
|
||||
const uuid = "01234567-89ab-cdef-0123-456789abcdef";
|
||||
const keys = buildTelemetryJoinKeys({
|
||||
feedbackId: uuid,
|
||||
anonymousId: uuid,
|
||||
recentRenders: Array.from({ length: 5 }, (_, i) => ({
|
||||
id: uuid,
|
||||
at: "2026-07-21T00:00:00Z",
|
||||
ok: i % 2 === 0,
|
||||
})),
|
||||
});
|
||||
// submitFeedback caps env at 500 chars; the doctor summary consumes ~100.
|
||||
expect(keys.length).toBeLessThan(400);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { failCommand } from "../utils/commandResult.js";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { resolve } from "node:path";
|
||||
import { defineCommand } from "citty";
|
||||
import * as clack from "@clack/prompts";
|
||||
@@ -7,6 +8,7 @@ import type { Example } from "./_examples.js";
|
||||
import { trackRenderFeedback } from "../telemetry/events.js";
|
||||
import { shouldTrack, flush } from "../telemetry/client.js";
|
||||
import { getDoctorSummary } from "../telemetry/feedback.js";
|
||||
import { readConfig, type RecentRenderRecord } from "../telemetry/config.js";
|
||||
import { publishProjectArchive } from "../utils/publishProject.js";
|
||||
import { submitFeedback } from "../utils/submitFeedback.js";
|
||||
import { buildIssueUrl, HYPERFRAMES_REPO_URL } from "../utils/feedbackIssue.js";
|
||||
@@ -28,6 +30,27 @@ function normalizeComment(raw?: string): string | undefined {
|
||||
return raw || undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact PostHog join keys appended to the environment string that rides
|
||||
* along with the forwarded report (and therefore lands verbatim in the wild
|
||||
* feedback channel): `fid` = this submission's PostHog `survey sent`
|
||||
* `feedback_id`; `tid` = the install's telemetry distinct_id; `renders` =
|
||||
* recent `render_job_id`s (newest last, `!` suffix = the render failed).
|
||||
* Together they turn a wild report into an exact telemetry lookup instead of
|
||||
* a hardware-fingerprint hunt.
|
||||
*/
|
||||
export function buildTelemetryJoinKeys(input: {
|
||||
feedbackId: string;
|
||||
anonymousId: string;
|
||||
recentRenders?: RecentRenderRecord[];
|
||||
}): string {
|
||||
const parts = [`fid=${input.feedbackId}`, `tid=${input.anonymousId}`];
|
||||
if (input.recentRenders?.length) {
|
||||
parts.push(`renders=${input.recentRenders.map((r) => `${r.id}${r.ok ? "" : "!"}`).join(",")}`);
|
||||
}
|
||||
return parts.join(" ");
|
||||
}
|
||||
|
||||
function printIssueConsent(dir: string): void {
|
||||
console.log();
|
||||
console.log(
|
||||
@@ -170,6 +193,18 @@ export default defineCommand({
|
||||
const comment = normalizeComment(args.comment);
|
||||
const doctorSummary = await getDoctorSummary();
|
||||
|
||||
// Join keys tying this report to the install's PostHog rows — see
|
||||
// buildTelemetryJoinKeys. Appended to the env string so they surface in
|
||||
// the forwarded report; mirrored as structured props on the PostHog event.
|
||||
const feedbackId = randomUUID();
|
||||
const config = readConfig();
|
||||
const joinKeys = buildTelemetryJoinKeys({
|
||||
feedbackId,
|
||||
anonymousId: config.anonymousId,
|
||||
recentRenders: config.recentRenders,
|
||||
});
|
||||
const envWithJoinKeys = doctorSummary ? `${doctorSummary} ${joinKeys}` : joinKeys;
|
||||
|
||||
// Soft-warn (never blocks) when the comment for a non-clean report is
|
||||
// missing the mandated reproduction-packet markers. Prints before the
|
||||
// submission ack so the reporter sees the nudge while their run is fresh.
|
||||
@@ -177,13 +212,19 @@ export default defineCommand({
|
||||
|
||||
// The standalone command runs separately from `render`, so it has no real
|
||||
// elapsed time to report. Omit it rather than recording a fake duration.
|
||||
trackRenderFeedback({ rating, comment, doctorSummary });
|
||||
trackRenderFeedback({
|
||||
rating,
|
||||
comment,
|
||||
doctorSummary,
|
||||
feedbackId,
|
||||
recentRenderIds: config.recentRenders?.map((r) => r.id),
|
||||
});
|
||||
|
||||
await flush();
|
||||
// Ack first so the user isn't kept waiting on the best-effort forward (which
|
||||
// is bounded to a few seconds and never surfaces an error either way).
|
||||
console.log(c.dim("Thanks for the feedback!"));
|
||||
await submitFeedback({ rating, comment, cliVersion: VERSION, env: doctorSummary });
|
||||
await submitFeedback({ rating, comment, cliVersion: VERSION, env: envWithJoinKeys });
|
||||
|
||||
if (args["file-issue"] === true) {
|
||||
await fileGithubIssue({
|
||||
|
||||
@@ -61,7 +61,12 @@ import {
|
||||
trackRenderObservation,
|
||||
} from "../telemetry/events.js";
|
||||
import { maybePromptRenderFeedback } from "../telemetry/feedback.js";
|
||||
import { readConfigFresh, writeConfig, type HyperframesConfig } from "../telemetry/config.js";
|
||||
import {
|
||||
readConfigFresh,
|
||||
recordRecentRender,
|
||||
writeConfig,
|
||||
type HyperframesConfig,
|
||||
} from "../telemetry/config.js";
|
||||
import { shouldTrack } from "../telemetry/client.js";
|
||||
import { renderJobObservabilityTelemetryPayload } from "../telemetry/renderObservability.js";
|
||||
import { bytesToMb } from "../telemetry/system.js";
|
||||
@@ -1339,6 +1344,9 @@ function handleRenderError(
|
||||
...renderJobObservabilityTelemetryPayload(job),
|
||||
...getMemorySnapshot(),
|
||||
});
|
||||
// Failed renders join the recent-renders ring too — a bug report filed via
|
||||
// `hyperframes feedback` is MOST likely to be about a failed render.
|
||||
if (job?.id) recordRecentRender(job.id, false);
|
||||
if (options.throwOnError) {
|
||||
throw new Error(message);
|
||||
}
|
||||
@@ -1376,6 +1384,9 @@ function trackRenderMetrics(
|
||||
options: RenderOptions,
|
||||
docker: boolean,
|
||||
): void {
|
||||
// Successful render → recent-renders ring, so a later `hyperframes
|
||||
// feedback` can attach this render's telemetry id to the report.
|
||||
recordRecentRender(job.id, true);
|
||||
const perf = job.perfSummary;
|
||||
const compositionDurationMs = perf
|
||||
? Math.round(perf.compositionDurationSeconds * 1000)
|
||||
@@ -1393,6 +1404,13 @@ function trackRenderMetrics(
|
||||
fps: fpsToNumber(options.fps),
|
||||
quality: options.quality,
|
||||
workers: options.workers ?? perf?.workers,
|
||||
workersBoundBy: perf?.workerSizing?.boundBy,
|
||||
workersCpuBased: perf?.workerSizing?.cpuBasedWorkers,
|
||||
workersMemoryBased: perf?.workerSizing?.memoryBasedWorkers,
|
||||
workersHeapBased: perf?.workerSizing?.heapBasedWorkers,
|
||||
workersFrameBased: perf?.workerSizing?.frameBasedWorkers,
|
||||
workersHeapLimitMb: perf?.workerSizing?.heapLimitMb,
|
||||
workersExceedHeapAdvisory: perf?.workerSizing?.exceedsHeapAdvisory,
|
||||
docker,
|
||||
gpu: options.gpu,
|
||||
authoringSkill: options.authoringSkill,
|
||||
|
||||
Reference in New Issue
Block a user