কর্মক্ষমতা অপ্টিমাইজ করা

নিয়ম লেখার সময়, পারফরম্যান্সের সবচেয়ে সাধারণ সমস্যা হল নির্ভরতা থেকে সঞ্চিত ডেটা অতিক্রম করা বা অনুলিপি করা। পুরো বিল্ডের উপর একত্রিত হলে, এই অপারেশনগুলি সহজেই O(N^2) সময় বা স্থান নিতে পারে। এটি এড়াতে, কীভাবে কার্যকরভাবে ডিপসেট ব্যবহার করতে হয় তা বোঝা গুরুত্বপূর্ণ।

এটি ঠিক করা কঠিন হতে পারে, তাই Bazel একটি মেমরি প্রোফাইলারও সরবরাহ করে যা আপনাকে এমন জায়গাগুলি খুঁজে পেতে সহায়তা করে যেখানে আপনি ভুল করেছেন। সতর্কতা অবলম্বন করুন: একটি অদক্ষ নিয়ম লেখার ব্যয়টি ব্যাপকভাবে ব্যবহার না হওয়া পর্যন্ত স্পষ্ট নাও হতে পারে।

ডিপসেট ব্যবহার করুন

যখনই আপনি নিয়ম নির্ভরতা থেকে তথ্য রোল আপ করছেন তখন আপনার depsets ব্যবহার করা উচিত। বর্তমান নিয়মে স্থানীয় তথ্য প্রকাশ করতে শুধুমাত্র প্লেইন তালিকা বা নির্দেশাবলী ব্যবহার করুন।

একটি ডিপসেট একটি নেস্টেড গ্রাফ হিসাবে তথ্য উপস্থাপন করে যা শেয়ারিং সক্ষম করে।

নিম্নলিখিত গ্রাফ বিবেচনা করুন:

C -> B -> A
D ---^

প্রতিটি নোড একটি একক স্ট্রিং প্রকাশ করে। ডিপসেটের সাথে ডেটা এইরকম দেখায়:

a = depset(direct=['a'])
b = depset(direct=['b'], transitive=[a])
c = depset(direct=['c'], transitive=[b])
d = depset(direct=['d'], transitive=[b])

উল্লেখ্য যে প্রতিটি আইটেম শুধুমাত্র একবার উল্লেখ করা হয়েছে. তালিকার সাথে আপনি এটি পাবেন:

a = ['a']
b = ['b', 'a']
c = ['c', 'b', 'a']
d = ['d', 'b', 'a']

উল্লেখ্য, এক্ষেত্রে 'a' চারবার উল্লেখ করা হয়েছে! বড় গ্রাফের সাথে এই সমস্যাটি আরও খারাপ হবে।

এখানে একটি নিয়ম বাস্তবায়নের একটি উদাহরণ রয়েছে যা ট্রানজিটিভ তথ্য প্রকাশ করতে সঠিকভাবে ডিপসেট ব্যবহার করে। মনে রাখবেন যে আপনি চাইলে তালিকা ব্যবহার করে নিয়ম-স্থানীয় তথ্য প্রকাশ করা ঠিক আছে যেহেতু এটি O(N^2) নয়।

MyProvider = provider()

def _impl(ctx):
  my_things = ctx.attr.things
  all_things = depset(
      direct=my_things,
      transitive=[dep[MyProvider].all_things for dep in ctx.attr.deps]
  )
  ...
  return [MyProvider(
    my_things=my_things,  # OK, a flat list of rule-local things only
    all_things=all_things,  # OK, a depset containing dependencies
  )]

আরও তথ্যের জন্য ডিপসেট ওভারভিউ পৃষ্ঠাটি দেখুন।

depset.to_list() কল করা এড়িয়ে চলুন

আপনি to_list() ব্যবহার করে একটি ফ্ল্যাট তালিকায় ডেপসেটকে বাধ্য করতে পারেন, কিন্তু এটি করার ফলে সাধারণত O(N^2) খরচ হয়। যদি সব সম্ভব হয়, ডিবাগিং উদ্দেশ্য ছাড়া ডিপসেটগুলিকে সমতল করা এড়িয়ে চলুন।

একটি সাধারণ ভ্রান্ত ধারণা হল যে আপনি অবাধে ডিপসেটগুলিকে সমতল করতে পারেন যদি আপনি এটি শুধুমাত্র শীর্ষ-স্তরের লক্ষ্যে করেন, যেমন একটি <xx>_binary নিয়ম, তারপর থেকে বিল্ড গ্রাফের প্রতিটি স্তরের উপর খরচ জমা হয় না। কিন্তু আপনি যখন ওভারল্যাপিং নির্ভরতা সহ লক্ষ্যগুলির একটি সেট তৈরি করেন তখনও এটি O(N^2)। আপনার পরীক্ষাগুলি তৈরি করার সময় এটি ঘটে //foo/tests/... , বা একটি IDE প্রকল্প আমদানি করার সময়।

depset কলের সংখ্যা কমিয়ে দিন

একটি লুপের ভিতরে depset কল করা প্রায়ই একটি ভুল। এটি খুব গভীর বাসা বাঁধার সাথে ডেপসেটের দিকে নিয়ে যেতে পারে, যা খারাপভাবে কাজ করে। উদাহরণ স্বরূপ:

x = depset()
for i in inputs:
    # Do not do that.
    x = depset(transitive = [x, i.deps])

এই কোড সহজেই প্রতিস্থাপন করা যেতে পারে. প্রথমে, ট্রানজিটিভ ডিপসেটগুলি সংগ্রহ করুন এবং সেগুলিকে একযোগে একত্রিত করুন:

transitive = []

for i in inputs:
    transitive.append(i.deps)

x = depset(transitive = transitive)

এটি কখনও কখনও একটি তালিকা বোঝার ব্যবহার করে হ্রাস করা যেতে পারে:

x = depset(transitive = [i.deps for i in inputs])

কমান্ড লাইনের জন্য ctx.actions.args() ব্যবহার করুন

কমান্ড লাইন তৈরি করার সময় আপনার ctx.actions.args() ব্যবহার করা উচিত। এটি কার্যকরী পর্যায়ে যেকোন ডিপসেটের সম্প্রসারণকে পিছিয়ে দেয়।

কঠোরভাবে দ্রুত হওয়ার পাশাপাশি, এটি আপনার নিয়মের মেমরি খরচ কমিয়ে দেবে -- কখনও কখনও 90% বা তার বেশি।

এখানে কিছু কৌশল আছে:

  • ডিপসেট এবং তালিকাগুলিকে নিজে সমতল করার পরিবর্তে আর্গুমেন্ট হিসাবে সরাসরি পাস করুন। সেগুলি আপনার জন্য ctx.actions.args() দ্বারা প্রসারিত হবে। আপনার যদি ডিপসেট বিষয়বস্তুতে কোনো রূপান্তরের প্রয়োজন হয়, তাহলে ctx.actions.args#add দেখুন বিলের সাথে কিছু খাপ খায় কিনা।

  • আপনি আর্গুমেন্ট হিসাবে File#path পাস করছেন? দরকার নেই. যে কোনো ফাইল স্বয়ংক্রিয়ভাবে তার পাথে পরিণত হয়, সম্প্রসারণের সময় স্থগিত করা হয়।

  • স্ট্রিংগুলিকে একত্রিত করে স্ট্রিং তৈরি করা এড়িয়ে চলুন। সেরা স্ট্রিং আর্গুমেন্ট হল একটি ধ্রুবক কারণ এটির মেমরি আপনার নিয়মের সমস্ত দৃষ্টান্তের মধ্যে ভাগ করা হবে।

  • যদি args কমান্ড লাইনের জন্য খুব দীর্ঘ হয় একটি ctx.actions.args() অবজেক্ট শর্তসাপেক্ষে বা নিঃশর্তভাবে ctx.actions.args#use_param_file ব্যবহার করে প্যারাম ফাইলে লেখা যেতে পারে। এটি পর্দার আড়ালে করা হয় যখন অ্যাকশনটি কার্যকর করা হয়। আপনি যদি প্যারামস ফাইলটিকে স্পষ্টভাবে নিয়ন্ত্রণ করতে চান তবে আপনি ctx.actions.write ব্যবহার করে ম্যানুয়ালি লিখতে পারেন।

উদাহরণ:

def _impl(ctx):
  ...
  args = ctx.actions.args()
  file = ctx.declare_file(...)
  files = depset(...)

  # Bad, constructs a full string "--foo=<file path>" for each rule instance
  args.add("--foo=" + file.path)

  # Good, shares "--foo" among all rule instances, and defers file.path to later
  # It will however pass ["--foo", <file path>] to the action command line,
  # instead of ["--foo=<file_path>"]
  args.add("--foo", file)

  # Use format if you prefer ["--foo=<file path>"] to ["--foo", <file path>]
  args.add(format="--foo=%s", value=file)

  # Bad, makes a giant string of a whole depset
  args.add(" ".join(["-I%s" % file.short_path for file in files])

  # Good, only stores a reference to the depset
  args.add_all(files, format_each="-I%s", map_each=_to_short_path)

# Function passed to map_each above
def _to_short_path(f):
  return f.short_path

ট্রানজিটিভ অ্যাকশন ইনপুট ডিপসেট হওয়া উচিত

ctx.actions.run ব্যবহার করে একটি অ্যাকশন তৈরি করার সময়, ভুলে যাবেন না যে inputs ক্ষেত্রটি একটি ডিপসেট গ্রহণ করে। যখনই নির্ভরশীলতা থেকে ইনপুট সংগ্রহ করা হয় তখন এটি ব্যবহার করুন।

inputs = depset(...)
ctx.actions.run(
  inputs = inputs,  # Do *not* turn inputs into a list
  ...
)

ঝুলন্ত

যদি ব্যাজেল হ্যাং হয়েছে বলে মনে হয়, তাহলে আপনি Ctrl-\ টিপুন অথবা $(bazel info output_base) $(bazel info output_base)/server/jvm.out ফাইলটিতে একটি থ্রেড ডাম্প পেতে Bazel-কে একটি SIGQUIT সংকেত পাঠাতে পারেন ( kill -3 $(bazel info server_pid) ) .

যেহেতু bazel হ্যাং হলে আপনি bazel info চালাতে পারবেন না, output_base ডিরেক্টরিটি সাধারণত আপনার ওয়ার্কস্পেস ডিরেক্টরিতে bazel-<workspace> এর প্যারেন্ট হয়।

কর্মক্ষমতা প্রোফাইলিং

Bazel ডিফল্টরূপে আউটপুট বেসে command.profile.gz এ একটি JSON প্রোফাইল লেখে। আপনি --profile পতাকা দিয়ে অবস্থান কনফিগার করতে পারেন, উদাহরণস্বরূপ --profile=/tmp/profile.gz.gz দিয়ে শেষ হওয়া অবস্থান GZIP দিয়ে সংকুচিত হয়।

ফলাফলগুলি দেখতে, একটি Chrome ব্রাউজার ট্যাবে chrome://tracing খুলুন, "লোড" ক্লিক করুন এবং (সম্ভাব্যভাবে সংকুচিত) প্রোফাইল ফাইলটি বাছাই করুন৷ আরও বিস্তারিত ফলাফলের জন্য, নীচের বাম কোণে বাক্সগুলিতে ক্লিক করুন৷

নেভিগেট করতে আপনি এই কীবোর্ড নিয়ন্ত্রণগুলি ব্যবহার করতে পারেন:

  • "নির্বাচন" মোডের জন্য 1 টিপুন। এই মোডে, আপনি ইভেন্টের বিবরণ পরিদর্শন করতে নির্দিষ্ট বাক্স নির্বাচন করতে পারেন (নীচের বাম কোণে দেখুন)। একটি সারাংশ এবং সমষ্টিগত পরিসংখ্যান পেতে একাধিক ইভেন্ট নির্বাচন করুন।
  • "প্যান" মোডের জন্য 2 টিপুন। তারপর ভিউ সরাতে মাউস টেনে আনুন। আপনি বাম/ডানে সরাতে a / d ব্যবহার করতে পারেন।
  • "জুম" মোডের জন্য 3 টিপুন। তারপর জুম করতে মাউস টেনে আনুন। জুম ইন/আউট করতে আপনি w / s ব্যবহার করতে পারেন।
  • "টাইমিং" মোডের জন্য 4 টিপুন যেখানে আপনি দুটি ইভেন্টের মধ্যে দূরত্ব পরিমাপ করতে পারেন।
  • প্রেস ? সমস্ত নিয়ন্ত্রণ সম্পর্কে জানতে।

জীবন তথ্য

উদাহরণ প্রোফাইল:

Example profile

চিত্র 1. উদাহরণ প্রোফাইল।

কিছু বিশেষ সারি আছে:

  • action counters : ফ্লাইটে কতগুলো সমসাময়িক ক্রিয়া আছে তা দেখায়। প্রকৃত মান দেখতে এটিতে ক্লিক করুন। পরিচ্ছন্ন --jobs -- চাকরির মান পর্যন্ত যেতে হবে।
  • cpu counters : বিল্ডের প্রতিটি সেকেন্ডের জন্য, Bazel দ্বারা ব্যবহৃত CPU-এর পরিমাণ প্রদর্শন করে (1 এর মান 100% ব্যস্ত থাকা এক কোরের সমান)।
  • Critical Path : ক্রিটিক্যাল পাথে প্রতিটি ক্রিয়ার জন্য একটি ব্লক প্রদর্শন করে।
  • grpc-command-1 : Bazel এর প্রধান থ্রেড। ব্যাজেল যা করছে তার একটি উচ্চ-স্তরের ছবি পেতে দরকারী, উদাহরণস্বরূপ "লঞ্চ বেজেল", "মূল্যায়ন টার্গেটপ্যাটার্নস", এবং "রান অ্যানালাইসিসফেজ"।
  • Service Thread : ছোট এবং বড় আবর্জনা সংগ্রহ (GC) বিরতি প্রদর্শন করে।

অন্যান্য সারি Bazel থ্রেড প্রতিনিধিত্ব করে এবং সেই থ্রেডের সমস্ত ইভেন্ট দেখায়।

সাধারণ কর্মক্ষমতা সমস্যা

কর্মক্ষমতা প্রোফাইল বিশ্লেষণ করার সময়, দেখুন:

  • প্রত্যাশিত বিশ্লেষণ পর্বের চেয়ে ধীর ( runAnalysisPhase ), বিশেষ করে ক্রমবর্ধমান বিল্ডগুলিতে। এটি একটি দুর্বল নিয়ম বাস্তবায়নের চিহ্ন হতে পারে, উদাহরণ স্বরূপ যেটি ডিপসেটকে সমতল করে। অত্যধিক লক্ষ্যমাত্রা, জটিল ম্যাক্রো বা পুনরাবৃত্ত গ্লোব দ্বারা প্যাকেজ লোডিং ধীর হতে পারে।
  • স্বতন্ত্র ধীর ক্রিয়া, বিশেষ করে যারা সমালোচনামূলক পথে। বড় ক্রিয়াগুলিকে একাধিক ছোট ক্রিয়াতে বিভক্ত করা বা তাদের গতি বাড়ানোর জন্য (ট্রানজিটিভ) নির্ভরতাগুলির সেট হ্রাস করা সম্ভব হতে পারে। এছাড়াও একটি অস্বাভাবিক উচ্চ নন- PROCESS_TIME (যেমন REMOTE_SETUP বা FETCH ) পরীক্ষা করুন৷
  • বটলনেকস, অর্থাৎ অল্প সংখ্যক থ্রেড ব্যস্ত থাকে যখন অন্যরা নিষ্ক্রিয় থাকে / ফলাফলের জন্য অপেক্ষা করে থাকে (উপরের স্ক্রিনশটে প্রায় 15s-30s দেখুন)। এটিকে অপ্টিমাইজ করার জন্য সম্ভবত নিয়ম বাস্তবায়ন বা বেজেলকে আরও সমান্তরালতার পরিচয় দিতে হবে। এটিও ঘটতে পারে যখন অস্বাভাবিক পরিমাণ GC থাকে।

প্রোফাইল ফাইল ফরম্যাট

শীর্ষ-স্তরের otherData মেটাডেটা (অন্যান্যডেটা) এবং প্রকৃত ট্রেসিং ডেটা (ট্রেস traceEvents ) থাকে। মেটাডেটা অতিরিক্ত তথ্য ধারণ করে, উদাহরণস্বরূপ আমন্ত্রণ আইডি এবং Bazel আহ্বানের তারিখ।

উদাহরণ:

{
  "otherData": {
    "build_id": "101bff9a-7243-4c1a-8503-9dc6ae4c3b05",
    "date": "Tue Jun 16 08:30:21 CEST 2020",
    "output_base": "/usr/local/google/_bazel_johndoe/573d4be77eaa72b91a3dfaa497bf8cd0"
  },
  "traceEvents": [
    {"name":"thread_name","ph":"M","pid":1,"tid":0,"args":{"name":"Critical Path"}},
    {"cat":"build phase marker","name":"Launch Bazel","ph":"X","ts":-1824000,"dur":1824000,"pid":1,"tid":60},
    ...
    {"cat":"general information","name":"NoSpawnCacheModule.beforeCommand","ph":"X","ts":116461,"dur":419,"pid":1,"tid":60},
    ...
    {"cat":"package creation","name":"src","ph":"X","ts":279844,"dur":15479,"pid":1,"tid":838},
    ...
    {"name":"thread_name","ph":"M","pid":1,"tid":11,"args":{"name":"Service Thread"}},
    {"cat":"gc notification","name":"minor GC","ph":"X","ts":334626,"dur":13000,"pid":1,"tid":11},

    ...
    {"cat":"action processing","name":"Compiling third_party/grpc/src/core/lib/transport/status_conversion.cc","ph":"X","ts":12630845,"dur":136644,"pid":1,"tid":1546}
 ]
}

ট্রেস ইভেন্টগুলিতে টাইমস্ট্যাম্প ( ts ) এবং সময়কাল ( dur ) মাইক্রোসেকেন্ডে দেওয়া হয়। ক্যাটাগরি ( cat ) হল ProfilerTask এর enum মানগুলির মধ্যে একটি। লক্ষ্য করুন যে কিছু ঘটনা একত্রে একত্রিত হয় যদি তারা খুব ছোট এবং একে অপরের কাছাকাছি হয়; আপনি যদি ইভেন্ট একত্রিত হওয়া প্রতিরোধ করতে চান তাহলে --noslim_json_profile পাস করুন।

এছাড়াও Chrome ট্রেস ইভেন্ট ফরম্যাট স্পেসিফিকেশন দেখুন।

বিশ্লেষণ-প্রোফাইল

এই প্রোফাইলিং পদ্ধতিটি দুটি ধাপ নিয়ে গঠিত, প্রথমে আপনাকে --profile পতাকা দিয়ে আপনার বিল্ড/পরীক্ষা চালাতে হবে, উদাহরণস্বরূপ

$ bazel build --profile=/tmp/prof //path/to:target

উত্পন্ন ফাইলটি (এই ক্ষেত্রে /tmp/prof ) একটি বাইনারি ফাইল, যা analyze-profile কমান্ড দ্বারা পোস্টপ্রসেস এবং বিশ্লেষণ করা যেতে পারে:

$ bazel analyze-profile /tmp/prof

ডিফল্টরূপে, এটি নির্দিষ্ট প্রোফাইল ডেটাফাইলের জন্য সারাংশ বিশ্লেষণ তথ্য প্রিন্ট করে। এতে প্রতিটি বিল্ড ফেজের জন্য বিভিন্ন ধরনের টাস্কের ক্রমবর্ধমান পরিসংখ্যান এবং সমালোচনামূলক পথের বিশ্লেষণ অন্তর্ভুক্ত রয়েছে।

ডিফল্ট আউটপুটের প্রথম বিভাগটি বিভিন্ন বিল্ড ফেজগুলিতে ব্যয় করা সময়ের একটি ওভারভিউ:

INFO: Profile created on Tue Jun 16 08:59:40 CEST 2020, build ID: 0589419c-738b-4676-a374-18f7bbc7ac23, output base: /home/johndoe/.cache/bazel/_bazel_johndoe/d8eb7a85967b22409442664d380222c0

=== PHASE SUMMARY INFORMATION ===

Total launch phase time         1.070 s   12.95%
Total init phase time           0.299 s    3.62%
Total loading phase time        0.878 s   10.64%
Total analysis phase time       1.319 s   15.98%
Total preparation phase time    0.047 s    0.57%
Total execution phase time      4.629 s   56.05%
Total finish phase time         0.014 s    0.18%
------------------------------------------------
Total run time                  8.260 s  100.00%

Critical path (4.245 s):
       Time Percentage   Description
    8.85 ms    0.21%   _Ccompiler_Udeps for @local_config_cc// compiler_deps
    3.839 s   90.44%   action 'Compiling external/com_google_protobuf/src/google/protobuf/compiler/php/php_generator.cc [for host]'
     270 ms    6.36%   action 'Linking external/com_google_protobuf/protoc [for host]'
    0.25 ms    0.01%   runfiles for @com_google_protobuf// protoc
     126 ms    2.97%   action 'ProtoCompile external/com_google_protobuf/python/google/protobuf/compiler/plugin_pb2.py'
    0.96 ms    0.02%   runfiles for //tools/aquery_differ aquery_differ

মেমরি প্রোফাইলিং

Bazel একটি অন্তর্নির্মিত মেমরি প্রোফাইলার সহ আসে যা আপনাকে আপনার নিয়মের মেমরি ব্যবহার পরীক্ষা করতে সাহায্য করতে পারে। যদি কোনও সমস্যা হয় তবে আপনি সমস্যাটি ঘটাচ্ছে এমন কোডের সঠিক লাইনটি খুঁজে পেতে হিপটি ডাম্প করতে পারেন।

মেমরি ট্র্যাকিং সক্ষম করা হচ্ছে

আপনাকে প্রতিটি Bazel আহ্বানে এই দুটি স্টার্টআপ পতাকা পাস করতে হবে:

  STARTUP_FLAGS=\
  --host_jvm_args=-javaagent:$(BAZEL)/third_party/allocation_instrumenter/java-allocation-instrumenter-3.3.0.jar \
  --host_jvm_args=-DRULE_MEMORY_TRACKER=1

এগুলো মেমরি ট্র্যাকিং মোডে সার্ভার শুরু করে। আপনি যদি একটি বেজেল আহ্বানের জন্যও এইগুলি ভুলে যান তবে সার্ভারটি পুনরায় চালু হবে এবং আপনাকে আবার শুরু করতে হবে।

মেমরি ট্র্যাকার ব্যবহার করে

একটি উদাহরণ হিসাবে, লক্ষ্য foo তাকান এবং দেখুন এটি কি করে। শুধুমাত্র বিশ্লেষণ চালানোর জন্য এবং বিল্ড এক্সিকিউশন ফেজ না চালানোর জন্য, --nobuild পতাকা যোগ করুন।

$ bazel $(STARTUP_FLAGS) build --nobuild //foo:foo

এর পরে, পুরো ব্যাজেল উদাহরণটি কত মেমরি খরচ করে তা দেখুন:

$ bazel $(STARTUP_FLAGS) info used-heap-size-after-gc
> 2594MB

bazel dump --rules ব্যবহার করে নিয়ম শ্রেণী দ্বারা এটি ভেঙে ফেলুন:

$ bazel $(STARTUP_FLAGS) dump --rules
>

RULE                                 COUNT     ACTIONS          BYTES         EACH
genrule                             33,762      33,801    291,538,824        8,635
config_setting                      25,374           0     24,897,336          981
filegroup                           25,369      25,369     97,496,272        3,843
cc_library                           5,372      73,235    182,214,456       33,919
proto_library                        4,140     110,409    186,776,864       45,115
android_library                      2,621      36,921    218,504,848       83,366
java_library                         2,371      12,459     38,841,000       16,381
_gen_source                            719       2,157      9,195,312       12,789
_check_proto_library_deps              719         668      1,835,288        2,552
... (more output)

bazel dump --skylark_memory ব্যবহার করে একটি pprof ফাইল তৈরি করে মেমরি কোথায় যাচ্ছে তা দেখুন :

$ bazel $(STARTUP_FLAGS) dump --skylark_memory=$HOME/prof.gz
> Dumping Starlark heap to: /usr/local/google/home/$USER/prof.gz

স্তূপ তদন্ত করতে pprof টুল ব্যবহার করুন. একটি ভাল সূচনা পয়েন্ট হল pprof -flame $HOME/prof.gz ব্যবহার করে একটি শিখা গ্রাফ পাওয়া।

https://github.com/google/pprof থেকে pprof পান।

লাইন সহ টীকাযুক্ত হটেস্ট কল সাইটগুলির একটি পাঠ্য ডাম্প পান:

$ pprof -text -lines $HOME/prof.gz
>
      flat  flat%   sum%        cum   cum%
  146.11MB 19.64% 19.64%   146.11MB 19.64%  android_library <native>:-1
  113.02MB 15.19% 34.83%   113.02MB 15.19%  genrule <native>:-1
   74.11MB  9.96% 44.80%    74.11MB  9.96%  glob <native>:-1
   55.98MB  7.53% 52.32%    55.98MB  7.53%  filegroup <native>:-1
   53.44MB  7.18% 59.51%    53.44MB  7.18%  sh_test <native>:-1
   26.55MB  3.57% 63.07%    26.55MB  3.57%  _generate_foo_files /foo/tc/tc.bzl:491
   26.01MB  3.50% 66.57%    26.01MB  3.50%  _build_foo_impl /foo/build_test.bzl:78
   22.01MB  2.96% 69.53%    22.01MB  2.96%  _build_foo_impl /foo/build_test.bzl:73
   ... (more output)