Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add telemetry to auto provisioning #7984

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-islands-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": patch
---

feat: add telemetry to experimental auto-provisioning
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a sanity check: This is a patch because it is an experimental feature, right?

66 changes: 66 additions & 0 deletions packages/wrangler/src/__tests__/provision.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { http, HttpResponse } from "msw";
import { logger } from "../logger";
import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
import { mockConsoleMethods } from "./helpers/mock-console";
import { clearDialogs, mockPrompt, mockSelect } from "./helpers/mock-dialogs";
@@ -30,6 +31,7 @@ describe("--x-provision", () => {
const { setIsTTY } = useMockIsTTY();

beforeEach(() => {
logger.loggerLevel = "debug";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might need to restore the loggerLevel after the test or it will create a side effect for the rest of the tests.

setIsTTY(true);
msw.use(
...mswSuccessDeploymentScriptMetadata,
@@ -103,6 +105,18 @@ describe("--x-provision", () => {
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);

expect(std.debug).toContain(
JSON.stringify(
{
"kv_namespace inherited": 1,
"d1 inherited": 1,
"r2_bucket inherited": 1,
},
undefined,
2
)
);
expect(std.err).toMatchInlineSnapshot(`""`);
expect(std.warn).toMatchInlineSnapshot(`""`);
});
@@ -208,6 +222,17 @@ describe("--x-provision", () => {
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);
expect(std.debug).toContain(
JSON.stringify(
{
"kv_namespace connected": 1,
"d1 connected": 1,
"r2_bucket connected": 1,
},
undefined,
2
)
);
expect(std.err).toMatchInlineSnapshot(`""`);
expect(std.warn).toMatchInlineSnapshot(`""`);
});
@@ -327,6 +352,17 @@ describe("--x-provision", () => {
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);
expect(std.debug).toContain(
JSON.stringify(
{
"kv_namespace connected": 1,
"d1 connected": 1,
"r2_bucket connected": 1,
},
undefined,
2
)
);
expect(std.err).toMatchInlineSnapshot(`""`);
expect(std.warn).toMatchInlineSnapshot(`""`);
});
@@ -459,6 +495,17 @@ describe("--x-provision", () => {
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);
expect(std.debug).toContain(
JSON.stringify(
{
"kv_namespace created": 1,
"d1 created": 1,
"r2_bucket created": 1,
},
undefined,
2
)
);
expect(std.err).toMatchInlineSnapshot(`""`);
expect(std.warn).toMatchInlineSnapshot(`""`);
});
@@ -524,6 +571,9 @@ describe("--x-provision", () => {
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);
expect(std.debug).toContain(
JSON.stringify({ "d1 created": 1 }, undefined, 2)
);
expect(std.err).toMatchInlineSnapshot(`""`);
expect(std.warn).toMatchInlineSnapshot(`""`);
});
@@ -566,6 +616,9 @@ describe("--x-provision", () => {
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);
expect(std.debug).toContain(
JSON.stringify({ "d1 inherited": 1 }, undefined, 2)
);
});

it("will not inherit d1 binding when the database name is provided but has changed", async () => {
@@ -643,6 +696,9 @@ describe("--x-provision", () => {
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);
expect(std.debug).toContain(
JSON.stringify({ "d1 created": 1 }, undefined, 2)
);
expect(std.err).toMatchInlineSnapshot(`""`);
expect(std.warn).toMatchInlineSnapshot(`""`);
});
@@ -716,6 +772,9 @@ describe("--x-provision", () => {
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);
expect(std.debug).toContain(
JSON.stringify({ "r2_bucket created": 1 }, undefined, 2)
);
expect(std.err).toMatchInlineSnapshot(`""`);
expect(std.warn).toMatchInlineSnapshot(`""`);
});
@@ -770,6 +829,7 @@ describe("--x-provision", () => {
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);
expect(std.debug).not.toContain("Provisioning summary");
expect(std.err).toMatchInlineSnapshot(`""`);
expect(std.warn).toMatchInlineSnapshot(`""`);
});
@@ -814,6 +874,9 @@ describe("--x-provision", () => {
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);
expect(std.debug).toContain(
JSON.stringify({ "d1 connected": 1 }, undefined, 2)
);
expect(std.err).toMatchInlineSnapshot(`""`);
expect(std.warn).toMatchInlineSnapshot(`""`);
});
@@ -898,6 +961,9 @@ describe("--x-provision", () => {
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);
expect(std.debug).toContain(
JSON.stringify({ "r2_bucket created": 1 }, undefined, 2)
);
expect(std.err).toMatchInlineSnapshot(`""`);
expect(std.warn).toMatchInlineSnapshot(`""`);
});
72 changes: 57 additions & 15 deletions packages/wrangler/src/deployment-bundle/bindings.ts
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import { prompt, select } from "../dialogs";
import { UserError } from "../errors";
import { createKVNamespace, listKVNamespaces } from "../kv/helpers";
import { logger } from "../logger";
import * as metrics from "../metrics";
import { APIError } from "../parse";
import { createR2Bucket, getR2Bucket, listR2Buckets } from "../r2/helpers";
import { isLegacyEnv } from "../utils/isLegacyEnv";
@@ -85,7 +86,9 @@ abstract class ProvisionResourceHandler<
public type: T,
public binding: B,
public idField: keyof B,
public accountId: string
public accountId: string,
/** For telemetry */
public summary: Record<string, number>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An instance of this class represents a single binding—it feels odd to be passing in a record of multiple bindings here? Why does this need to be done insdie the class? Can't we just count the number of items in pendingResources?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah it is odd :/ i wanted access to the inherit/create/connect methods to record what happens to each resource, not just that they get provisioned in some way (connected or created), especially as some resources get connected before runProvisioningFlow and some after.

Although now that i'm revisiting it, i think that's probably a level of granularity that isn't necessary/important and we can just count pendingResources... yeah i'll simplify this

) {}

// Does this resource already exist in the currently deployed version of the Worker?
@@ -95,10 +98,16 @@ abstract class ProvisionResourceHandler<
): boolean | Promise<boolean>;

inherit(): void {
this.summary[`${this.type} inherited`] =
(this.summary[`${this.type} inherited`] ?? 0) + 1;
// @ts-expect-error idField is a key of this.binding
this.binding[this.idField] = INHERIT_SYMBOL;
}
connect(id: string): void {
connect(id: string, record: boolean = true): void {
if (record) {
this.summary[`${this.type} connected`] =
(this.summary[`${this.type} connected`] ?? 0) + 1;
}
// @ts-expect-error idField is a key of this.binding
this.binding[this.idField] = id;
}
@@ -108,8 +117,10 @@ abstract class ProvisionResourceHandler<
abstract get name(): string | undefined;

async provision(name: string): Promise<void> {
this.summary[`${this.type} created`] =
(this.summary[`${this.type} created`] ?? 0) + 1;
const id = await this.create(name);
this.connect(id);
this.connect(id, false);
}

// This binding is fully specified and can't/shouldn't be provisioned
@@ -164,8 +175,12 @@ class R2Handler extends ProvisionResourceHandler<"r2_bucket", CfR2Bucket> {
);
return name;
}
constructor(binding: CfR2Bucket, accountId: string) {
super("r2_bucket", binding, "bucket_name", accountId);
constructor(
binding: CfR2Bucket,
accountId: string,
summary: Record<string, number>
) {
super("r2_bucket", binding, "bucket_name", accountId, summary);
}
canInherit(settings: Settings | undefined): boolean {
return !!settings?.bindings.find(
@@ -212,8 +227,12 @@ class KVHandler extends ProvisionResourceHandler<
async create(name: string) {
return await createKVNamespace(this.accountId, name);
}
constructor(binding: CfKvNamespace, accountId: string) {
super("kv_namespace", binding, "id", accountId);
constructor(
binding: CfKvNamespace,
accountId: string,
summary: Record<string, number>
) {
super("kv_namespace", binding, "id", accountId, summary);
}
canInherit(settings: Settings | undefined): boolean {
return !!settings?.bindings.find(
@@ -234,8 +253,12 @@ class D1Handler extends ProvisionResourceHandler<"d1", CfD1Database> {
const db = await createD1Database(this.accountId, name);
return db.uuid;
}
constructor(binding: CfD1Database, accountId: string) {
super("d1", binding, "database_id", accountId);
constructor(
binding: CfD1Database,
accountId: string,
summary: Record<string, number>
) {
super("d1", binding, "database_id", accountId, summary);
}
async canInherit(settings: Settings | undefined): Promise<boolean> {
const maybeInherited = settings?.bindings.find(
@@ -341,8 +364,13 @@ async function collectPendingResources(
accountId: string,
scriptName: string,
bindings: CfWorkerInit["bindings"]
): Promise<PendingResource[]> {
): Promise<{
pendingResources: PendingResource[];
summary: Record<string, number>;
}> {
let settings: Settings | undefined;
// summarises what happens to the resources to send as one telemetry event
const summary: Record<string, number> = {};

try {
settings = await getSettings(accountId, scriptName);
@@ -361,7 +389,11 @@ async function collectPendingResources(
HANDLERS
) as (keyof typeof HANDLERS)[]) {
for (const resource of bindings[resourceType] ?? []) {
const h = new HANDLERS[resourceType].Handler(resource, accountId);
const h = new HANDLERS[resourceType].Handler(
resource,
accountId,
summary
);

if (await h.shouldProvision(settings)) {
pendingResources.push({
@@ -373,18 +405,22 @@ async function collectPendingResources(
}
}

return pendingResources.sort(
(a, b) => HANDLERS[a.resourceType].sort - HANDLERS[b.resourceType].sort
);
return {
pendingResources: pendingResources.sort(
(a, b) => HANDLERS[a.resourceType].sort - HANDLERS[b.resourceType].sort
),
summary,
};
}

export async function provisionBindings(
bindings: CfWorkerInit["bindings"],
accountId: string,
scriptName: string,
autoCreate: boolean,
config: Config
): Promise<void> {
const pendingResources = await collectPendingResources(
const { pendingResources, summary } = await collectPendingResources(
accountId,
scriptName,
bindings
@@ -422,6 +458,12 @@ export async function provisionBindings(

logger.log(`🎉 All resources provisioned, continuing with deployment...\n`);
}
if (Object.keys(summary).length) {
metrics.sendMetricsEvent("provision resources", summary, {
sendMetrics: config.send_metrics,
});
logger.debug("Provisioning summary:", JSON.stringify(summary, null, 2));
}
}

function getSettings(accountId: string, scriptName: string) {
3 changes: 2 additions & 1 deletion packages/wrangler/src/metrics/send-event.ts
Original file line number Diff line number Diff line change
@@ -81,7 +81,8 @@ export type EventNames =
| "list pipelines"
| "delete pipeline"
| "update pipeline"
| "show pipeline";
| "show pipeline"
| "provision resources";

/**
* Send a metrics event, with no extra properties, to Cloudflare, if usage tracking is enabled.