-
Notifications
You must be signed in to change notification settings - Fork 803
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[D1] teach wrangler how to fetch insights about D1's queries (#4909)
* [D1] teach wrangler how to fetch insights about D1's queries * add options for sorting and number of results * add a means of filtering the datetime geq/leq * add more options, fix tests * add wrangler banner, add warning text * add changeset * rename last to timePeriod * use string format over numbers as >1mo's data could be possible * Update red-icons-flow.md * make it possible to order by count * Update red-icons-flow.md * PR feedback * Update .changeset/red-icons-flow.md Co-authored-by: Pete Bacon Darwin <[email protected]> * address PR feedback --------- Co-authored-by: Pete Bacon Darwin <[email protected]>
- Loading branch information
1 parent
e61dba5
commit 34b6ea1
Showing
6 changed files
with
239 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
--- | ||
"wrangler": patch | ||
--- | ||
|
||
feat: add an experimental `insights` command to `wrangler d1` | ||
|
||
This PR adds a `wrangler d1 insights <DB_NAME>` command, to let D1 users figure out which of their queries to D1 need to be optimised. | ||
|
||
This command defaults to fetching the top 5 queries that took the longest to run in total over the last 24 hours. | ||
|
||
You can also fetch the top 5 queries that consumed the most rows read over the last week, for example: | ||
|
||
```bash | ||
npx wrangler d1 insights northwind --sortBy reads --timePeriod 7d | ||
``` | ||
|
||
Or the top 5 queries that consumed the most rows written over the last month, for example: | ||
|
||
```bash | ||
npx wrangler d1 insights northwind --sortBy writes --timePeriod 31d | ||
``` | ||
|
||
Or the top 5 most frequently run queries in the last 24 hours, for example: | ||
|
||
```bash | ||
npx wrangler d1 insights northwind --sortBy count | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import { printWranglerBanner } from ".."; | ||
import { fetchGraphqlResult } from "../cfetch"; | ||
import { withConfig } from "../config"; | ||
import { logger } from "../logger"; | ||
import { requireAuth } from "../user"; | ||
import { | ||
d1BetaWarning, | ||
getDatabaseByNameOrBinding, | ||
getDatabaseInfoFromId, | ||
} from "./utils"; | ||
import type { | ||
CommonYargsArgv, | ||
StrictYargsOptionsToInterface, | ||
} from "../yargs-types"; | ||
import type { D1QueriesGraphQLResponse, Database } from "./types"; | ||
|
||
export function Options(d1ListYargs: CommonYargsArgv) { | ||
return d1ListYargs | ||
.positional("name", { | ||
describe: "The name of the DB", | ||
type: "string", | ||
demandOption: true, | ||
}) | ||
.option("timePeriod", { | ||
choices: ["1d", "7d", "31d"] as const, | ||
describe: "Fetch data from now to the provided time period", | ||
default: "1d" as const, | ||
}) | ||
.option("sort-type", { | ||
choices: ["sum", "avg"] as const, | ||
describe: "Choose the operation you want to sort insights by", | ||
default: "sum" as const, | ||
}) | ||
.option("sort-by", { | ||
choices: ["time", "reads", "writes", "count"] as const, | ||
describe: "Choose the field you want to sort insights by", | ||
default: "time" as const, | ||
}) | ||
.option("sort-direction", { | ||
choices: ["ASC", "DESC"] as const, | ||
describe: "Choose a sort direction", | ||
default: "DESC" as const, | ||
}) | ||
.option("count", { | ||
describe: "fetch insights about the first X queries", | ||
type: "number", | ||
default: 5, | ||
}) | ||
.option("json", { | ||
describe: "return output as clean JSON", | ||
type: "boolean", | ||
default: false, | ||
}) | ||
.epilogue(d1BetaWarning); | ||
} | ||
|
||
const cliOptionToGraphQLOption = { | ||
time: "queryDurationMs", | ||
reads: "rowsRead", | ||
writes: "rowsWritten", | ||
count: "count", | ||
}; | ||
|
||
type HandlerOptions = StrictYargsOptionsToInterface<typeof Options>; | ||
export const Handler = withConfig<HandlerOptions>( | ||
async ({ | ||
name, | ||
config, | ||
json, | ||
count, | ||
timePeriod, | ||
sortType, | ||
sortBy, | ||
sortDirection, | ||
}): Promise<void> => { | ||
const accountId = await requireAuth(config); | ||
const db: Database = await getDatabaseByNameOrBinding( | ||
config, | ||
accountId, | ||
name | ||
); | ||
|
||
const result = await getDatabaseInfoFromId(accountId, db.uuid); | ||
|
||
const output: Record<string, string | number>[] = []; | ||
|
||
if (result.version !== "alpha") { | ||
const convertedTimePeriod = Number(timePeriod.replace("d", "")); | ||
const endDate = new Date(); | ||
const startDate = new Date( | ||
new Date(endDate).setDate(endDate.getDate() - convertedTimePeriod) | ||
); | ||
const parsedSortBy = cliOptionToGraphQLOption[sortBy]; | ||
const orderByClause = | ||
parsedSortBy === "count" | ||
? `${parsedSortBy}_${sortDirection}` | ||
: `${sortType}_${parsedSortBy}_${sortDirection}`; | ||
const graphqlQueriesResult = | ||
await fetchGraphqlResult<D1QueriesGraphQLResponse>({ | ||
method: "POST", | ||
body: JSON.stringify({ | ||
query: `query getD1QueriesOverviewQuery($accountTag: string, $filter: ZoneWorkersRequestsFilter_InputObject) { | ||
viewer { | ||
accounts(filter: {accountTag: $accountTag}) { | ||
d1QueriesAdaptiveGroups(limit: ${count}, filter: $filter, orderBy: [${orderByClause}]) { | ||
sum { | ||
queryDurationMs | ||
rowsRead | ||
rowsWritten | ||
} | ||
avg { | ||
queryDurationMs | ||
rowsRead | ||
rowsWritten | ||
} | ||
count | ||
dimensions { | ||
query | ||
} | ||
} | ||
} | ||
} | ||
}`, | ||
operationName: "getD1QueriesOverviewQuery", | ||
variables: { | ||
accountTag: accountId, | ||
filter: { | ||
AND: [ | ||
{ | ||
datetimeHour_geq: startDate.toISOString(), | ||
datetimeHour_leq: endDate.toISOString(), | ||
databaseId: db.uuid, | ||
}, | ||
], | ||
}, | ||
}, | ||
}), | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
|
||
graphqlQueriesResult?.data?.viewer?.accounts[0]?.d1QueriesAdaptiveGroups?.forEach( | ||
(row) => { | ||
if (!row.dimensions.query) return; | ||
output.push({ | ||
query: row.dimensions.query, | ||
avgRowsRead: row?.avg?.rowsRead ?? 0, | ||
totalRowsRead: row?.sum?.rowsRead ?? 0, | ||
avgRowsWritten: row?.avg?.rowsWritten ?? 0, | ||
totalRowsWritten: row?.sum?.rowsWritten ?? 0, | ||
avgDurationMs: row?.avg?.queryDurationMs ?? 0, | ||
totalDurationMs: row?.sum?.queryDurationMs ?? 0, | ||
numberOfTimesRun: row?.count ?? 0, | ||
}); | ||
} | ||
); | ||
} | ||
|
||
if (json) { | ||
logger.log(JSON.stringify(output, null, 2)); | ||
} else { | ||
await printWranglerBanner(); | ||
logger.log( | ||
"-------------------\n🚧 `wrangler d1 insights` is an experimental command.\n🚧 Flags for this command, their descriptions, and output may change between wrangler versions.\n-------------------\n" | ||
); | ||
logger.log(JSON.stringify(output, null, 2)); | ||
} | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters