Skip to content

Commit

Permalink
[Query API] Clean-Up Query Definitions (#1065)
Browse files Browse the repository at this point in the history
* refactor: basic SupportedQueries registry structure

* feat: allow registering an ascii summarizer for a query directly

* refactor: include query schemas in query definitions

* feat-fix: fixed meta queries being included in print

* refactor: move query defs into their own files
  • Loading branch information
Ellpeck authored Oct 15, 2024
1 parent 1f8cc5b commit 0fb4c89
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 138 deletions.
3 changes: 2 additions & 1 deletion src/cli/repl/commands/repl-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { splitAtEscapeSensitive } from '../../../util/args';
import { italic } from '../../../util/ansi';
import { describeSchema } from '../../../util/schema';
import type { Query, QueryResults, SupportedQueryTypes } from '../../../queries/query';
import { executeQueries } from '../../../queries/query';
import { executeQueries } from '../../../queries/query';

import type { PipelineOutput } from '../../../core/steps/pipeline/pipeline';
import { jsonReplacer } from '../../../util/json';
import { AnyQuerySchema, QueriesSchema } from '../../../queries/query-schema';
Expand Down
72 changes: 8 additions & 64 deletions src/documentation/doc-util/doc-query.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { RShell } from '../../r-bridge/shell';
import type { Queries, QueryResults, SupportedQueryTypes } from '../../queries/query';
import { executeQueries } from '../../queries/query';
import { SupportedQueries , executeQueries } from '../../queries/query';

import { PipelineExecutor } from '../../core/pipeline-executor';
import { DEFAULT_DATAFLOW_PIPELINE } from '../../core/steps/pipeline/default-pipelines';
import { requestFromInput } from '../../r-bridge/retriever';
Expand All @@ -15,10 +16,7 @@ import { printAsMs } from '../../util/time';
import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
import type { PipelineOutput } from '../../core/steps/pipeline/pipeline';
import { BuiltIn } from '../../dataflow/environments/built-in';
import { graphToMermaidUrl } from '../../util/mermaid/dfg';
import { normalizedAstToMermaidUrl } from '../../util/mermaid/ast';
import type { StaticSliceQuery } from '../../queries/catalog/static-slice-query/static-slice-query-format';
import type { BaseQueryMeta } from '../../queries/base-query-format';
import type { BaseQueryMeta, BaseQueryResult } from '../../queries/base-query-format';
import { textWithTooltip } from './doc-hover-over';
import type { CallContextQuerySubKindResult } from '../../queries/catalog/call-context-query/call-context-query-format';

Expand Down Expand Up @@ -61,7 +59,7 @@ function asciiCallContextSubHit(formatter: OutputFormatter, results: readonly Ca
return result.join(', ');
}

function asciiCallContext(formatter: OutputFormatter, results: QueryResults<'call-context'>['call-context'], processed: PipelineOutput<typeof DEFAULT_DATAFLOW_PIPELINE>): string {
export function asciiCallContext(formatter: OutputFormatter, results: QueryResults<'call-context'>['call-context'], processed: PipelineOutput<typeof DEFAULT_DATAFLOW_PIPELINE>): string {
/* traverse over 'kinds' and within them 'subkinds' */
const result: string[] = [];
for(const [kind, { subkinds }] of Object.entries(results['kinds'])) {
Expand All @@ -73,7 +71,7 @@ function asciiCallContext(formatter: OutputFormatter, results: QueryResults<'cal
return result.join('\n');
}

function summarizeIdsIfTooLong(ids: readonly NodeId[]) {
export function summarizeIdsIfTooLong(ids: readonly NodeId[]) {
const naive = ids.join(', ');
if(naive.length <= 20) {
return naive;
Expand All @@ -96,63 +94,9 @@ export function asciiSummaryOfQueryResult(formatter: OutputFormatter, totalInMs:
if(query === '.meta') {
continue;
}
if(query === 'call-context') {
const out = queryResults as QueryResults<'call-context'>['call-context'];
result.push(`Query: ${bold(query, formatter)} (${printAsMs(out['.meta'].timing, 0)})`);
result.push(asciiCallContext(formatter, out, processed));
continue;
} else if(query === 'dataflow') {
const out = queryResults as QueryResults<'dataflow'>['dataflow'];
result.push(`Query: ${bold(query, formatter)} (${printAsMs(out['.meta'].timing, 0)})`);
result.push(` ╰ [Dataflow Graph](${graphToMermaidUrl(out.graph)})`);
continue;
} else if(query === 'id-map') {
const out = queryResults as QueryResults<'id-map'>['id-map'];
result.push(`Query: ${bold(query, formatter)} (${printAsMs(out['.meta'].timing, 0)})`);
result.push(` ╰ Id List: {${summarizeIdsIfTooLong([...out.idMap.keys()])}}`);
continue;
} else if(query === 'normalized-ast') {
const out = queryResults as QueryResults<'normalized-ast'>['normalized-ast'];
result.push(`Query: ${bold(query, formatter)} (${printAsMs(out['.meta'].timing, 0)})`);
result.push(` ╰ [Normalized AST](${normalizedAstToMermaidUrl(out.normalized.ast)})`);
continue;
} else if(query === 'static-slice') {
const out = queryResults as QueryResults<'static-slice'>['static-slice'];
result.push(`Query: ${bold(query, formatter)} (${printAsMs(out['.meta'].timing, 0)})`);
for(const [fingerprint, obj] of Object.entries(out.results)) {
const { criteria, noMagicComments, noReconstruction } = JSON.parse(fingerprint) as StaticSliceQuery;
const addons = [];
if(noReconstruction) {
addons.push('no reconstruction');
}
if(noMagicComments) {
addons.push('no magic comments');
}
result.push(` ╰ Slice for {${criteria.join(', ')}} ${addons.join(', ')}`);
if('reconstruct' in obj) {
result.push(' ╰ Code (newline as <code>&#92;n</code>): <code>' + obj.reconstruct.code.split('\n').join('\\n') + '</code>');
} else {
result.push(` ╰ Id List: {${summarizeIdsIfTooLong([...obj.slice.result])}}`);
}
}
continue;
} else if(query === 'dataflow-cluster') {
const out = queryResults as QueryResults<'dataflow-cluster'>['dataflow-cluster'];
result.push(`Query: ${bold(query, formatter)} (${out['.meta'].timing.toFixed(0)}ms)`);
result.push(` ╰ Found ${out.clusters.length} cluster${out.clusters.length === 1 ? '' : 's'}`);
for(const cluster of out.clusters) {
const unknownSideEffects = cluster.hasUnknownSideEffects ? '(has unknown side effect)' : '';
result.push(` ╰ ${unknownSideEffects} {${summarizeIdsIfTooLong(cluster.members)}} ([marked](${
graphToMermaidUrl(processed.dataflow.graph, false, new Set(cluster.members))
}))`);
}
continue;
} else if(query === 'lineage') {
const out = queryResults as QueryResults<'lineage'>['lineage'];
result.push(`Query: ${bold(query, formatter)} (${printAsMs(out['.meta'].timing, 0)})`);
for(const [criteria, lineage] of Object.entries(out.lineages)) {
result.push(` ╰ ${criteria}: {${summarizeIdsIfTooLong([...lineage])}}`);
}

const queryType = SupportedQueries[query as SupportedQueryTypes];
if(queryType.asciiSummarizer(formatter, processed, queryResults as BaseQueryResult, result)) {
continue;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { BaseQueryFormat, BaseQueryResult } from '../../base-query-format';
import type { NodeId } from '../../../r-bridge/lang-4.x/ast/model/processing/node-id';
import { executeCallContextQueries } from './call-context-query-executor';
import { bold } from '../../../util/ansi';
import { printAsMs } from '../../../util/time';
import Joi from 'joi';
import type { QueryResults, SupportedQuery } from '../../query';
import { asciiCallContext } from '../../../documentation/doc-util/doc-query';

export enum CallTargets {
/** call targets a function that is not defined locally (e.g., the call targets a library function) */
Expand Down Expand Up @@ -83,3 +89,25 @@ export interface CallContextQueryResult extends BaseQueryResult {

export type CallContextQuery<CallName extends RegExp | string = RegExp | string> = DefaultCallContextQueryFormat<CallName> | SubCallContextQueryFormat<CallName>;

export const CallContextQueryDefinition = {
executor: executeCallContextQueries,
asciiSummarizer: (formatter, processed, queryResults, result) => {
const out = queryResults as QueryResults<'call-context'>['call-context'];
result.push(`Query: ${bold('call-context', formatter)} (${printAsMs(out['.meta'].timing, 0)})`);
result.push(asciiCallContext(formatter, out, processed));
return true;
},
schema: Joi.object({
type: Joi.string().valid('call-context').required().description('The type of the query.'),
callName: Joi.string().required().description('Regex regarding the function name!'),
callNameExact: Joi.boolean().optional().description('Should we automatically add the `^` and `$` anchors to the regex to make it an exact match?'),
kind: Joi.string().optional().description('The kind of the call, this can be used to group calls together (e.g., linking `plot` to `visualize`). Defaults to `.`'),
subkind: Joi.string().optional().description('The subkind of the call, this can be used to uniquely identify the respective call type when grouping the output (e.g., the normalized name, linking `ggplot` to `plot`). Defaults to `.`'),
callTargets: Joi.string().valid(...Object.values(CallTargets)).optional().description('Call targets the function may have. This defaults to `any`. Request this specifically to gain all call targets we can resolve.'),
includeAliases: Joi.boolean().optional().description('Consider a case like `f <- function_of_interest`, do you want uses of `f` to be included in the results?'),
linkTo: Joi.object({
type: Joi.string().valid('link-to-last-call').required().description('The type of the linkTo sub-query.'),
callName: Joi.string().required().description('Regex regarding the function name of the last call. Similar to `callName`, strings are interpreted as a regular expression.')
}).optional().description('Links the current call to the last call of the given kind. This way, you can link a call like `points` to the latest graphics plot etc.')
}).description('Call context query used to find calls in the dataflow graph')
} as const satisfies SupportedQuery<'call-context'>;
25 changes: 25 additions & 0 deletions src/queries/catalog/cluster-query/cluster-query-format.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { BaseQueryFormat, BaseQueryResult } from '../../base-query-format';
import { bold } from '../../../util/ansi';
import Joi from 'joi';
import type { QueryResults, SupportedQuery } from '../../query';
import type { DataflowGraphClusters } from '../../../dataflow/cluster';
import { executeDataflowClusterQuery } from './cluster-query-executor';
import { summarizeIdsIfTooLong } from '../../../documentation/doc-util/doc-query';
import { graphToMermaidUrl } from '../../../util/mermaid/dfg';

/**
* Calculates and returns all clusters encountered in the dataflow graph.
Expand All @@ -12,3 +18,22 @@ export interface DataflowClusterQueryResult extends BaseQueryResult {
/** All clusters found in the respective dataflow */
readonly clusters: DataflowGraphClusters;
}

export const ClusterQueryDefinition = {
executor: executeDataflowClusterQuery,
asciiSummarizer: (formatter, processed, queryResults, result) => {
const out = queryResults as QueryResults<'dataflow-cluster'>['dataflow-cluster'];
result.push(`Query: ${bold('dataflow-cluster', formatter)} (${out['.meta'].timing.toFixed(0)}ms)`);
result.push(` ╰ Found ${out.clusters.length} cluster${out.clusters.length === 1 ? '' : 's'}`);
for(const cluster of out.clusters) {
const unknownSideEffects = cluster.hasUnknownSideEffects ? '(has unknown side effect)' : '';
result.push(` ╰ ${unknownSideEffects} {${summarizeIdsIfTooLong(cluster.members)}} ([marked](${
graphToMermaidUrl(processed.dataflow.graph, false, new Set(cluster.members))
}))`);
}
return true;
},
schema: Joi.object({
type: Joi.string().valid('dataflow-cluster').required().description('The type of the query.'),
}).description('The cluster query calculates and returns all clusters in the dataflow graph.')
} as const satisfies SupportedQuery<'dataflow-cluster'>;
19 changes: 19 additions & 0 deletions src/queries/catalog/dataflow-query/dataflow-query-format.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { BaseQueryFormat, BaseQueryResult } from '../../base-query-format';
import type { DataflowGraph } from '../../../dataflow/graph/graph';
import { executeDataflowQuery } from './dataflow-query-executor';
import { bold } from '../../../util/ansi';
import { printAsMs } from '../../../util/time';
import { graphToMermaidUrl } from '../../../util/mermaid/dfg';
import Joi from 'joi';
import type { QueryResults, SupportedQuery } from '../../query';

/**
* Simple re-returns the dataflow graph of the analysis.
Expand All @@ -12,3 +18,16 @@ export interface DataflowQueryResult extends BaseQueryResult {
/** Please be aware that this is the graph in its JSON representation, use {@link DataflowGraph#fromJson} if the result is serialized */
readonly graph: DataflowGraph;
}

export const DataflowQueryDefinition = {
executor: executeDataflowQuery,
asciiSummarizer: (formatter, _processed, queryResults, result) => {
const out = queryResults as QueryResults<'dataflow'>['dataflow'];
result.push(`Query: ${bold('dataflow', formatter)} (${printAsMs(out['.meta'].timing, 0)})`);
result.push(` ╰ [Dataflow Graph](${graphToMermaidUrl(out.graph)})`);
return true;
},
schema: Joi.object({
type: Joi.string().valid('dataflow').required().description('The type of the query.'),
}).description('The dataflow query simply returns the dataflow graph, there is no need to pass it multiple times!')
} as const satisfies SupportedQuery<'dataflow'>;
19 changes: 19 additions & 0 deletions src/queries/catalog/id-map-query/id-map-query-format.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { BaseQueryFormat, BaseQueryResult } from '../../base-query-format';
import type { AstIdMap } from '../../../r-bridge/lang-4.x/ast/model/processing/decorate';
import { executeIdMapQuery } from './id-map-query-executor';
import { bold } from '../../../util/ansi';
import { printAsMs } from '../../../util/time';
import Joi from 'joi';
import type { QueryResults, SupportedQuery } from '../../query';
import { summarizeIdsIfTooLong } from '../../../documentation/doc-util/doc-query';

export interface IdMapQuery extends BaseQueryFormat {
readonly type: 'id-map';
Expand All @@ -8,3 +14,16 @@ export interface IdMapQuery extends BaseQueryFormat {
export interface IdMapQueryResult extends BaseQueryResult {
readonly idMap: AstIdMap;
}

export const IdMapQueryDefinition = {
executor: executeIdMapQuery,
asciiSummarizer: (formatter, _processed, queryResults, result) => {
const out = queryResults as QueryResults<'id-map'>['id-map'];
result.push(`Query: ${bold('id-map', formatter)} (${printAsMs(out['.meta'].timing, 0)})`);
result.push(` ╰ Id List: {${summarizeIdsIfTooLong([...out.idMap.keys()])}}`);
return true;
},
schema: Joi.object({
type: Joi.string().valid('id-map').required().description('The type of the query.'),
}).description('The id map query retrieves the id map from the normalized AST.')
} as const satisfies SupportedQuery<'id-map'>;
22 changes: 22 additions & 0 deletions src/queries/catalog/lineage-query/lineage-query-format.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import type { BaseQueryFormat, BaseQueryResult } from '../../base-query-format';
import type { SingleSlicingCriterion } from '../../../slicing/criterion/parse';
import type { NodeId } from '../../../r-bridge/lang-4.x/ast/model/processing/node-id';
import type { QueryResults, SupportedQuery } from '../../query';
import { bold } from '../../../util/ansi';
import { printAsMs } from '../../../util/time';
import Joi from 'joi';
import { executeLineageQuery } from './lineage-query-executor';
import { summarizeIdsIfTooLong } from '../../../documentation/doc-util/doc-query';

/**
* Calculates the lineage of the given criterion.
Expand All @@ -14,3 +20,19 @@ export interface LineageQueryResult extends BaseQueryResult {
/** Maps each criterion to the found lineage, duplicates are ignored. */
readonly lineages: Record<SingleSlicingCriterion, Set<NodeId>>;
}

export const LineageQueryDefinition = {
executor: executeLineageQuery,
asciiSummarizer: (formatter, _processed, queryResults, result) => {
const out = queryResults as QueryResults<'lineage'>['lineage'];
result.push(`Query: ${bold('lineage', formatter)} (${printAsMs(out['.meta'].timing, 0)})`);
for(const [criteria, lineage] of Object.entries(out.lineages)) {
result.push(` ╰ ${criteria}: {${summarizeIdsIfTooLong([...lineage])}}`);
}
return true;
},
schema: Joi.object({
type: Joi.string().valid('lineage').required().description('The type of the query.'),
id: Joi.string().required().description('The ID of the node to get the lineage of.')
}).description('Lineage query used to find the lineage of a node in the dataflow graph')
} as const satisfies SupportedQuery<'lineage'>;
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { BaseQueryFormat, BaseQueryResult } from '../../base-query-format';
import type { NormalizedAst } from '../../../r-bridge/lang-4.x/ast/model/processing/decorate';
import { executeNormalizedAstQuery } from './normalized-ast-query-executor';
import { bold } from '../../../util/ansi';
import { printAsMs } from '../../../util/time';
import { normalizedAstToMermaidUrl } from '../../../util/mermaid/ast';
import Joi from 'joi';
import type { QueryResults, SupportedQuery } from '../../query';

/**
* Simple re-returns the normalized AST of the analysis.
Expand All @@ -11,3 +17,16 @@ export interface NormalizedAstQuery extends BaseQueryFormat {
export interface NormalizedAstQueryResult extends BaseQueryResult {
readonly normalized: NormalizedAst;
}

export const NormalizedAstQueryDefinition = {
executor: executeNormalizedAstQuery,
asciiSummarizer: (formatter, _processed, queryResults, result) => {
const out = queryResults as QueryResults<'normalized-ast'>['normalized-ast'];
result.push(`Query: ${bold('normalized-ast', formatter)} (${printAsMs(out['.meta'].timing, 0)})`);
result.push(` ╰ [Normalized AST](${normalizedAstToMermaidUrl(out.normalized.ast)})`);
return true;
},
schema: Joi.object({
type: Joi.string().valid('normalized-ast').required().description('The type of the query.'),
}).description('The normalized AST query simply returns the normalized AST, there is no need to pass it multiple times!')
} as const satisfies SupportedQuery<'normalized-ast'>;
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import type {
DEFAULT_SLICING_PIPELINE
} from '../../../core/steps/pipeline/default-pipelines';
import type { SlicingCriteria } from '../../../slicing/criterion/parse';
import type { QueryResults, SupportedQuery } from '../../query';
import { bold } from '../../../util/ansi';
import { printAsMs } from '../../../util/time';
import Joi from 'joi';
import { executeStaticSliceClusterQuery } from './static-slice-query-executor';
import { summarizeIdsIfTooLong } from '../../../documentation/doc-util/doc-query';

/** Calculates and returns all clusters encountered in the dataflow graph. */
export interface StaticSliceQuery extends BaseQueryFormat {
Expand All @@ -30,3 +36,34 @@ export interface StaticSliceQueryResult extends BaseQueryResult {
Omit<PipelineOutput<typeof DEFAULT_SLICE_WITHOUT_RECONSTRUCT_PIPELINE>, keyof PipelineOutput<typeof DEFAULT_DATAFLOW_PIPELINE>>
>
}

export const StaticSliceQueryDefinition = {
executor: executeStaticSliceClusterQuery,
asciiSummarizer: (formatter, _processed, queryResults, result) => {
const out = queryResults as QueryResults<'static-slice'>['static-slice'];
result.push(`Query: ${bold('static-slice', formatter)} (${printAsMs(out['.meta'].timing, 0)})`);
for(const [fingerprint, obj] of Object.entries(out.results)) {
const { criteria, noMagicComments, noReconstruction } = JSON.parse(fingerprint) as StaticSliceQuery;
const addons = [];
if(noReconstruction) {
addons.push('no reconstruction');
}
if(noMagicComments) {
addons.push('no magic comments');
}
result.push(` ╰ Slice for {${criteria.join(', ')}} ${addons.join(', ')}`);
if('reconstruct' in obj) {
result.push(' ╰ Code (newline as <code>&#92;n</code>): <code>' + obj.reconstruct.code.split('\n').join('\\n') + '</code>');
} else {
result.push(` ╰ Id List: {${summarizeIdsIfTooLong([...obj.slice.result])}}`);
}
}
return true;
},
schema: Joi.object({
type: Joi.string().valid('static-slice').required().description('The type of the query.'),
criteria: Joi.array().items(Joi.string()).min(0).required().description('The slicing criteria to use.'),
noReconstruction: Joi.boolean().optional().description('Do not reconstruct the slice into readable code.'),
noMagicComments: Joi.boolean().optional().description('Should the magic comments (force-including lines within the slice) be ignored?')
}).description('Slice query used to slice the dataflow graph')
} as const satisfies SupportedQuery<'static-slice'>;
Loading

2 comments on commit 0fb4c89

@github-actions
Copy link

Choose a reason for hiding this comment

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

"artificial" Benchmark Suite

Benchmark suite Current: 0fb4c89 Previous: 96e933e Ratio
Retrieve AST from R code 242.58943245454546 ms (104.56651762412716) 246.50067822727272 ms (108.0778534921102) 0.98
Normalize R AST 17.73581309090909 ms (32.527363715907676) 17.822801272727272 ms (32.47501104567212) 1.00
Produce dataflow information 39.703889909090904 ms (86.37101360427793) 40.41399131818182 ms (86.99830114822697) 0.98
Total per-file 824.6891270454545 ms (1494.9985372112235) 829.6500495454545 ms (1498.0426737610057) 0.99
Static slicing 2.052650232245993 ms (1.2133766800485262) 2.0971564864344034 ms (1.2085738336418979) 0.98
Reconstruct code 0.2312634388241616 ms (0.17675075091119086) 0.24421041798721546 ms (0.1931884846808244) 0.95
Total per-slice 2.298858309335852 ms (1.278605960482032) 2.356082671207842 ms (1.2841644653561477) 0.98
failed to reconstruct/re-parse 0 # 0 # 1
times hit threshold 0 # 0 # 1
reduction (characters) 0.7869360165281424 # 0.7869360165281424 # 1
reduction (normalized tokens) 0.7639690077689504 # 0.7639690077689504 # 1
memory (df-graph) 95.46617542613636 KiB (244.77619956879823) 95.46617542613636 KiB (244.77619956879823) 1

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions
Copy link

Choose a reason for hiding this comment

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

"social-science" Benchmark Suite

Benchmark suite Current: 0fb4c89 Previous: 96e933e Ratio
Retrieve AST from R code 252.41028164 ms (47.04610482443627) 239.76002830000002 ms (44.476154875177215) 1.05
Normalize R AST 19.7371189 ms (15.699432895929682) 19.04045506 ms (14.770405721401682) 1.04
Produce dataflow information 77.00090884000001 ms (92.01566709180948) 74.39786466 ms (87.80796950166253) 1.03
Total per-file 7835.06353164 ms (29166.121590012757) 7666.33215246 ms (28737.408915639426) 1.02
Static slicing 16.162837690289233 ms (44.61512226720604) 15.907723437298863 ms (43.83669809749617) 1.02
Reconstruct code 0.29575570313164307 ms (0.1673812060121803) 0.2509487217116593 ms (0.14943631432024615) 1.18
Total per-slice 16.467127194681055 ms (44.64656827987436) 16.166379499642126 ms (43.873427530614464) 1.02
failed to reconstruct/re-parse 0 # 0 # 1
times hit threshold 0 # 0 # 1
reduction (characters) 0.8712997340230448 # 0.8712997340230448 # 1
reduction (normalized tokens) 0.8102441553774778 # 0.8102441553774778 # 1
memory (df-graph) 99.8990234375 KiB (113.72812769327498) 99.8990234375 KiB (113.72812769327498) 1

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.