Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit 42ff3eb

Browse files
committed
added detailed logs
1 parent f3db4bf commit 42ff3eb

File tree

3 files changed

+100
-6
lines changed

3 files changed

+100
-6
lines changed

gapic/cli/generate.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import os
1616
import sys
1717
import typing
18+
import time
1819

1920
import click
2021

@@ -24,6 +25,13 @@
2425
from gapic.schema import api
2526
from gapic.utils import Options
2627

28+
# <--- Profiling Global --->
29+
LOG_FILE = "/tmp/gapic_profile.log"
30+
31+
def _log(msg):
32+
with open(LOG_FILE, "a", encoding="utf-8") as f:
33+
f.write(f"[{time.strftime('%H:%M:%S')}] [CLI] {msg}\n")
34+
# <--- End Profiling Global --->
2735

2836
@click.command()
2937
@click.option(
@@ -42,8 +50,19 @@
4250
)
4351
def generate(request: typing.BinaryIO, output: typing.BinaryIO) -> None:
4452
"""Generate a full API client description."""
53+
54+
# <--- Start Profiling --->
55+
# We clear the file here since this is the entry point
56+
with open(LOG_FILE, "w", encoding="utf-8") as f:
57+
f.write("--- CLI PROCESS START ---\n")
58+
59+
t_start_script = time.time()
60+
# <--- End Profiling --->
61+
4562
# Load the protobuf CodeGeneratorRequest.
63+
t0 = time.time()
4664
req = plugin_pb2.CodeGeneratorRequest.FromString(request.read())
65+
_log(f"Load CodeGeneratorRequest took {time.time() - t0:.4f}s")
4766

4867
# Pull apart arguments in the request.
4968
opts = Options.build(req.parameter)
@@ -59,15 +78,33 @@ def generate(request: typing.BinaryIO, output: typing.BinaryIO) -> None:
5978
# Build the API model object.
6079
# This object is a frozen representation of the whole API, and is sent
6180
# to each template in the rendering step.
81+
# <--- Profile API Build --->
82+
_log("Starting API.build (Parsing Protos)...")
83+
t0 = time.time()
84+
6285
api_schema = api.API.build(req.proto_file, opts=opts, package=package)
86+
87+
_log(f"API.build took {time.time() - t0:.4f}s")
88+
# <--- End Profile API Build --->
6389

6490
# Translate into a protobuf CodeGeneratorResponse; this reads the
6591
# individual templates and renders them.
6692
# If there are issues, error out appropriately.
93+
# <--- Profile Generator --->
94+
_log("Starting generator.get_response (Rendering Templates)...")
95+
t0 = time.time()
96+
6797
res = generator.Generator(opts).get_response(api_schema, opts)
98+
99+
_log(f"generator.get_response took {time.time() - t0:.4f}s")
100+
# <--- End Profile Generator --->
68101

69102
# Output the serialized response.
103+
t0 = time.time()
70104
output.write(res.SerializeToString())
105+
_log(f"Serialization/Write took {time.time() - t0:.4f}s")
106+
107+
_log(f"TOTAL CLI RUNTIME: {time.time() - t_start_script:.4f}s")
71108

72109

73110
if __name__ == "__main__":

gapic/generator/generator.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import os
2020
import pathlib
2121
import typing
22+
import time
23+
import sys
2224
from typing import Any, DefaultDict, Dict, Mapping, Optional, Tuple
2325
from hashlib import sha256
2426
from collections import OrderedDict, defaultdict
@@ -34,8 +36,17 @@
3436
from gapic.schema import api
3537
from gapic import utils
3638
from gapic.utils import Options
39+
from gapic.utils import rst as rst_module
3740
from google.protobuf.compiler.plugin_pb2 import CodeGeneratorResponse
3841

42+
# <--- Profiling Global --->
43+
LOG_FILE = "/tmp/gapic_profile.log"
44+
45+
def _log(msg):
46+
# Append mode so we don't wipe logs from previous steps/APIs
47+
with open(LOG_FILE, "a", encoding="utf-8") as f:
48+
f.write(f"[{time.strftime('%H:%M:%S')}] {msg}\n")
49+
# <--- End Profiling Global --->
3950

4051
class Generator:
4152
"""A protoc code generator for client libraries.
@@ -91,6 +102,11 @@ def get_response(self, api_schema: api.API, opts: Options) -> CodeGeneratorRespo
91102
~.CodeGeneratorResponse: A response describing appropriate
92103
files and contents. See ``plugin.proto``.
93104
"""
105+
# <--- Profiling Start --->
106+
_log(f"--- GENERATION STARTED (get_response) FOR {api_schema.naming.proto_package} ---")
107+
start_time = time.time() # FIXED: Variable name matches end usage
108+
# <--- Profiling End --->
109+
94110
output_files: Dict[str, CodeGeneratorResponse.File] = OrderedDict()
95111
sample_templates, client_templates = utils.partition(
96112
lambda fname: os.path.basename(fname) == samplegen.DEFAULT_TEMPLATE_NAME,
@@ -101,13 +117,15 @@ def get_response(self, api_schema: api.API, opts: Options) -> CodeGeneratorRespo
101117
# can be inserted into method docstrings.
102118
snippet_idx = snippet_index.SnippetIndex(api_schema)
103119
if sample_templates:
120+
t_samples = time.time()
104121
sample_output, snippet_idx = self._generate_samples_and_manifest(
105122
api_schema,
106123
snippet_idx,
107124
self._env.get_template(sample_templates[0]),
108125
opts=opts,
109126
)
110127
output_files.update(sample_output)
128+
_log(f"Phase: Sample Gen took {time.time() - t_samples:.4f}s")
111129

112130
# Iterate over each template and add the appropriate output files
113131
# based on that template.
@@ -119,8 +137,9 @@ def get_response(self, api_schema: api.API, opts: Options) -> CodeGeneratorRespo
119137
filename = template_name.split("/")[-1]
120138
if filename.startswith("_") and filename != "__init__.py.j2":
121139
continue
122-
123-
# Append to the output files dictionary.
140+
141+
# <--- Profiling Template --->
142+
t_tpl = time.time()
124143
output_files.update(
125144
self._render_template(
126145
template_name,
@@ -129,12 +148,18 @@ def get_response(self, api_schema: api.API, opts: Options) -> CodeGeneratorRespo
129148
snippet_index=snippet_idx,
130149
)
131150
)
151+
duration = time.time() - t_tpl
152+
if duration > 1.0:
153+
_log(f"Phase: Template [{template_name}] took {duration:.4f}s")
154+
# <--- End Profiling Template --->
132155

133156
# Return the CodeGeneratorResponse output.
134157
res = CodeGeneratorResponse(
135158
file=[i for i in output_files.values()]
136159
) # type: ignore
137160
res.supported_features |= CodeGeneratorResponse.Feature.FEATURE_PROTO3_OPTIONAL # type: ignore
161+
162+
_log(f"TOTAL GENERATION COMPLETE (get_response): {time.time() - start_time:.4f}s")
138163
return res
139164

140165
def _generate_samples_and_manifest(
@@ -400,6 +425,10 @@ def _get_file(
400425
context=context,
401426
)
402427

428+
# <--- Profiling Render Start --->
429+
t_render = time.time()
430+
# <--- End Profiling Render Start --->
431+
403432
# Render the file contents.
404433
cgr_file = CodeGeneratorResponse.File(
405434
content=formatter.fix_whitespace(
@@ -410,6 +439,12 @@ def _get_file(
410439
name=fn,
411440
)
412441

442+
# <--- Profiling Render End --->
443+
duration = time.time() - t_render
444+
if duration > 0.5:
445+
_log(f" > RENDER: {fn} ({duration:.4f}s)")
446+
# <--- End Profiling Render End --->
447+
413448
# Quick check: Do not render empty files.
414449
if utils.empty(cgr_file.content) and not fn.endswith(
415450
("py.typed", "__init__.py")

gapic/schema/api.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@
6464
from gapic.utils import Options
6565
from gapic.utils import to_snake_case
6666
from gapic.utils import RESERVED_NAMES
67+
import time
68+
69+
LOG_FILE = "/tmp/gapic_profile.log"
70+
71+
def _log(msg):
72+
# Append mode so we don't wipe logs from previous steps/APIs
73+
with open(LOG_FILE, "a", encoding="utf-8") as f:
74+
f.write(f"[{time.strftime('%H:%M:%S')}] {msg}\n")
6775

6876

6977
TRANSPORT_GRPC = "grpc"
@@ -456,7 +464,9 @@ def disambiguate_keyword_sanitize_fname(
456464
# load the services and methods with the full scope of types.
457465
pre_protos: Dict[str, Proto] = dict(prior_protos or {})
458466
for fd in file_descriptors:
467+
t0 = time.time()
459468
fd.name = disambiguate_keyword_sanitize_fname(fd.name, pre_protos)
469+
is_target = fd.package.startswith(package)
460470
pre_protos[fd.name] = Proto.build(
461471
file_descriptor=fd,
462472
file_to_generate=fd.package.startswith(package),
@@ -466,6 +476,9 @@ def disambiguate_keyword_sanitize_fname(
466476
# Ugly, ugly hack.
467477
load_services=False,
468478
)
479+
if is_target:
480+
duration = time.time() - t0
481+
_log(f"API.build (Pass 1 - Messages Only): {fd.name} took {duration:.4f}s")
469482

470483
# A file descriptor's file-level resources are NOT visible to any importers.
471484
# The only way to make referenced resources visible is to aggregate them at
@@ -477,24 +490,33 @@ def disambiguate_keyword_sanitize_fname(
477490
# Second pass uses all the messages and enums defined in the entire API.
478491
# This allows LRO returning methods to see all the types in the API,
479492
# bypassing the above missing import problem.
480-
protos: Dict[str, Proto] = {
481-
name: Proto.build(
493+
protos: Dict[str, Proto] = {}
494+
495+
for name, proto in pre_protos.items():
496+
t0 = time.time()
497+
498+
protos[name] = Proto.build(
482499
file_descriptor=proto.file_pb2,
483500
file_to_generate=proto.file_to_generate,
484501
naming=naming,
485502
opts=opts,
486503
prior_protos=pre_protos,
487504
all_resources=MappingProxyType(all_file_resources),
488505
)
489-
for name, proto in pre_protos.items()
490-
}
506+
507+
# Log timing only for the target file
508+
if proto.file_to_generate:
509+
duration = time.time() - t0
510+
_log(f"API.build (Pass 2): {name} took {duration:.4f}s")
491511

492512
# Parse the google.api.Service proto from the service_yaml data.
513+
t0_yaml = time.time()
493514
service_yaml_config = service_pb2.Service()
494515
ParseDict(
495516
opts.service_yaml_config, service_yaml_config, ignore_unknown_fields=True
496517
)
497518
gapic_version = opts.gapic_version
519+
_log(f"API.build (Service YAML Parse) took {time.time() - t0_yaml:.4f}s")
498520

499521
# Third pass for various selective GAPIC settings; these require
500522
# settings in the service.yaml and so we build the API object

0 commit comments

Comments
 (0)