Skip to content

Commit

Permalink
feat: Query Profile (#2014)
Browse files Browse the repository at this point in the history
* Add QueryMode protos.

* WIP: Query profiling APIs.

* Use generic type.

* WIP WIP.

* WIP WIP WIP.

* Update integration tests.

* clean up.

* lint.

* Fix: Do not re-use _stream().

* Address more feedback.

* introduce QueryPlan class to make it possible to add node-level info in future.

* fix lint errors.

* Revert host setting.

* undo manual proto updates.

* Update the public APIs to v2.

* Reuse existing _stream methods.

* Introduce ExplainMetrics and PlanSummary.

* Update tests.

* Manually import query profile protos.

* WIP: Update impl and tests with new protos.

* WIP (2): Update impl and tests with new protos.

* WIP (3): Update impl and tests with new protos.

* WIP (4): Update impl and tests with new protos.

* Add test for explainStream.

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

* Remove bytesReturned from the API.

* minor improvements.

* minor improvements.

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

* improve documentation.

* Fix unit test failure.

* minor improvements.

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

* explain options should be optional.

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

* Address feedback.

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

---------

Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
Co-authored-by: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com>
  • Loading branch information
3 people committed Mar 25, 2024
1 parent c65cef0 commit 9a45ec8
Show file tree
Hide file tree
Showing 9 changed files with 997 additions and 57 deletions.
7 changes: 3 additions & 4 deletions dev/src/aggregate.ts
@@ -1,12 +1,11 @@
/**
* @license
* Copyright 2023 Google LLC
/*!
* Copyright 2023 Google LLC. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
Expand Down
41 changes: 41 additions & 0 deletions dev/src/convert.ts
Expand Up @@ -161,6 +161,47 @@ export function detectValueType(proto: ProtobufJsValue): string {
return detectedValues[0];
}

/**
* Detects the value kind from a Proto3 JSON `google.protobuf.Value` proto.
*
* @private
* @internal
* @param proto The `firestore.v1.Value` proto.
* @return The string value for 'valueType'.
*/
export function detectGoogleProtobufValueType(
proto: google.protobuf.IValue
): string {
const detectedValues: string[] = [];

if (proto.nullValue !== undefined) {
detectedValues.push('nullValue');
}
if (proto.numberValue !== undefined) {
detectedValues.push('numberValue');
}
if (proto.stringValue !== undefined) {
detectedValues.push('stringValue');
}
if (proto.boolValue !== undefined) {
detectedValues.push('boolValue');
}
if (proto.structValue !== undefined) {
detectedValues.push('structValue');
}
if (proto.listValue !== undefined) {
detectedValues.push('listValue');
}

if (detectedValues.length !== 1) {
throw new Error(
`Unable to infer type value from '${JSON.stringify(proto)}'.`
);
}

return detectedValues[0];
}

/**
* Converts a `firestore.v1.Value` in Proto3 JSON encoding into the
* Protobuf JS format expected by this client.
Expand Down
6 changes: 6 additions & 0 deletions dev/src/index.ts
Expand Up @@ -114,6 +114,12 @@ export type {
AggregateSpec,
AggregateType,
} from './aggregate';
export type {
PlanSummary,
ExecutionStats,
ExplainMetrics,
ExplainResults,
} from './query-profile';

const libVersion = require('../../package.json').version;
setLibVersion(libVersion);
Expand Down
139 changes: 139 additions & 0 deletions dev/src/query-profile.ts
@@ -0,0 +1,139 @@
/*!
* Copyright 2024 Google LLC. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as firestore from '@google-cloud/firestore';
import {google} from '../protos/firestore_v1_proto_api';
import {Serializer} from './serializer';
import IPlanSummary = google.firestore.v1.IPlanSummary;
import IExecutionStats = google.firestore.v1.IExecutionStats;
import IExplainMetrics = google.firestore.v1.IExplainMetrics;

/**
* PlanSummary contains information about the planning stage of a query.
*
* @class PlanSummary
*/
export class PlanSummary implements firestore.PlanSummary {
/**
* @private
* @internal
*/
constructor(readonly indexesUsed: Record<string, unknown>[]) {}

/**
* @private
* @internal
*/
static _fromProto(
plan: IPlanSummary | null | undefined,
serializer: Serializer
): PlanSummary {
const indexes: Record<string, unknown>[] = [];
if (plan && plan.indexesUsed) {
for (const index of plan.indexesUsed) {
indexes.push(serializer.decodeGoogleProtobufStruct(index));
}
}
return new PlanSummary(indexes);
}
}

/**
* ExecutionStats contains information about the execution of a query.
*
* @class ExecutionStats
*/
export class ExecutionStats implements firestore.ExecutionStats {
/**
* @private
* @internal
*/
constructor(
readonly resultsReturned: number,
readonly executionDuration: firestore.Duration,
readonly readOperations: number,
readonly debugStats: Record<string, unknown>
) {}

/**
* @private
* @internal
*/
static _fromProto(
stats: IExecutionStats | null | undefined,
serializer: Serializer
): ExecutionStats | null {
if (stats) {
return new ExecutionStats(
Number(stats.resultsReturned),
{
seconds: Number(stats.executionDuration?.seconds),
nanoseconds: Number(stats.executionDuration?.nanos),
},
Number(stats.readOperations),
serializer.decodeGoogleProtobufStruct(stats.debugStats)
);
}
return null;
}
}

/**
* ExplainMetrics contains information about planning and execution of a query.
*
* @class ExplainMetrics
*/
export class ExplainMetrics implements firestore.ExplainMetrics {
/**
* @private
* @internal
*/
constructor(
readonly planSummary: PlanSummary,
readonly executionStats: ExecutionStats | null
) {}

/**
* @private
* @internal
*/
static _fromProto(
metrics: IExplainMetrics,
serializer: Serializer
): ExplainMetrics {
return new ExplainMetrics(
PlanSummary._fromProto(metrics.planSummary, serializer),
ExecutionStats._fromProto(metrics.executionStats, serializer)
);
}
}

/**
* ExplainResults contains information about planning, execution, and results
* of a query.
*
* @class ExplainResults
*/
export class ExplainResults<T> implements firestore.ExplainResults<T> {
/**
* @private
* @internal
*/
constructor(
readonly metrics: ExplainMetrics,
readonly snapshot: T | null
) {}
}

0 comments on commit 9a45ec8

Please sign in to comment.