xref: /aosp_15_r20/external/perfetto/src/trace_processor/importers/ninja/ninja_log_parser.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "src/trace_processor/importers/ninja/ninja_log_parser.h"
18 
19 #include <stdio.h>
20 #include <algorithm>
21 #include <cstdint>
22 #include <cstring>
23 #include <vector>
24 
25 #include "perfetto/base/status.h"
26 #include "perfetto/ext/base/string_splitter.h"
27 #include "perfetto/ext/base/string_utils.h"
28 #include "perfetto/ext/base/string_view.h"
29 #include "perfetto/trace_processor/trace_blob_view.h"
30 #include "src/trace_processor/importers/common/process_tracker.h"
31 #include "src/trace_processor/importers/common/slice_tracker.h"
32 #include "src/trace_processor/importers/common/track_tracker.h"
33 #include "src/trace_processor/storage/stats.h"
34 #include "src/trace_processor/storage/trace_storage.h"
35 
36 namespace perfetto::trace_processor {
37 
38 using base::StringSplitter;
39 
NinjaLogParser(TraceProcessorContext * ctx)40 NinjaLogParser::NinjaLogParser(TraceProcessorContext* ctx) : ctx_(ctx) {}
41 NinjaLogParser::~NinjaLogParser() = default;
42 
Parse(TraceBlobView blob)43 base::Status NinjaLogParser::Parse(TraceBlobView blob) {
44   // A trace is read in chunks of arbitrary size (for http fetch() pipeliniing),
45   // not necessarily aligned on a line boundary.
46   // Here we push everything into a vector and, on each call, consume only
47   // the leading part until the last \n, keeping the rest for the next call.
48   const char* src = reinterpret_cast<const char*>(blob.data());
49   log_.insert(log_.end(), src, src + blob.size());
50 
51   // Find the last \n.
52   size_t valid_size = log_.size();
53   for (; valid_size > 0 && log_[valid_size - 1] != '\n'; --valid_size) {
54   }
55 
56   for (StringSplitter line(log_.data(), valid_size, '\n'); line.Next();) {
57     static const char kHeader[] = "# ninja log v";
58     if (!header_parsed_) {
59       if (!base::StartsWith(line.cur_token(), kHeader))
60         return base::ErrStatus("Failed to parse ninja log header");
61       header_parsed_ = true;
62       auto version = base::CStringToUInt32(line.cur_token() + strlen(kHeader));
63       if (!version || *version != 5)
64         return base::ErrStatus("Unsupported ninja log version");
65       continue;
66     }
67 
68     // Each line in the ninja log looks like this:
69     // 4 12  1579224178  ui/assets/modal.scss  832a958a9e234dfa
70     // Where:
71     // - [4, 12] are the timestamps in ms of [start, end] of the job, measured
72     //     from the beginning of the build.
73     // - 1579224178 is the "restat" (ignored).
74     // - ui/assets/modal.scss is the name of the output file being built.
75     // - 832a958a9e234dfa is a hash of the compiler invocation.
76     // In most cases, each hash should be unique per ninja invocation (because
77     // two rules shouln't generate the same output). However, in rare
78     // circumstances the same hash can show up more than once. Examples:
79     // - A GN action generates > 1 output per invocation (e.g., protos). In this
80     //   case all items will have the same [start, end] timestamp. In this case
81     //   we want to merge all the output names into one build step, because from
82     //   the build system viewpoint, that was the same compiler/tool invocation.
83     // - A subtle script that generates different outputs without taking a
84     //   --output=filename argument (e.g. via env vars or similar). Note that
85     //   this happens in the perfetto codebase itself (goto.google.com/nigew).
86     //   In this case we want to treat the two entries as two distinct jobs.
87     //
88     // In summary the deduping logic here is: if both the hash and the
89     // timestamps match -> merge, if not, keep distinct.
90     StringSplitter tok(&line, '\t');
91     auto t_start = base::CStringToInt64(tok.Next() ? tok.cur_token() : "");
92     auto t_end = base::CStringToInt64(tok.Next() ? tok.cur_token() : "");
93     tok.Next();  // Ignore restat.
94     const char* name = tok.Next() ? tok.cur_token() : nullptr;
95     auto cmdhash = base::CStringToUInt64(tok.Next() ? tok.cur_token() : "", 16);
96 
97     if (!t_start || !t_end || !name || !cmdhash) {
98       ctx_->storage->IncrementStats(stats::ninja_parse_errors);
99       continue;
100     }
101 
102     // If more hashes show up back-to-back with the same timestamps, merge them
103     // together as they identify multiple outputs for the same build rule.
104     // TODO(lalitm): this merging should really happen in NotifyEndOfFile
105     // because we want to merge across builds. However, this needs some
106     // non-significant rework of this class so it's not been found to be worth
107     // implementing yet.
108     if (!jobs_.empty() && *cmdhash == jobs_.back().hash &&
109         *t_start == jobs_.back().start_ms && *t_end == jobs_.back().end_ms) {
110       jobs_.back().names.append(" ");
111       jobs_.back().names.append(name);
112       continue;
113     }
114 
115     jobs_.emplace_back(*t_start, *t_end, *cmdhash, name);
116   }
117   log_.erase(log_.begin(), log_.begin() + static_cast<ssize_t>(valid_size));
118   return base::OkStatus();
119 }
120 
121 // This is called after the last Parse() call. At this point all |jobs_| have
122 // been populated.
NotifyEndOfFile()123 base::Status NinjaLogParser::NotifyEndOfFile() {
124   std::sort(jobs_.begin(), jobs_.end(),
125             [](const Job& x, const Job& y) { return x.start_ms < y.start_ms; });
126 
127   // Now we need to work out the job parallelism. There's no direct indication
128   // of that in the ninja logs, so it must be inferred by observing overlapping
129   // of timestamps. In this context a "Worker" is an inferred sequence of jobs
130   // that happened concurrently with other sequences.
131   // Here we pack jobs according the following heuristic, for the sake of making
132   // the graph nicer to read to humans. Consider the initial situation:
133   // 1: [  job 1 ]
134   // 2:   [   job 2   ]
135   // 3: [   job 3   ]
136   //    T=0              | T=6
137   // Assume that a new job starts at T=6. It's very likely that job4 was started
138   // as a consequence of job2 completion (othewise it could have been started
139   // earlier, soon after job 1 or Job 3). It seems to make more sense to draw
140   // it next in the 2nd worker, i.e. next to job 2.
141   struct Worker {
142     int64_t busy_until;
143     TrackId track_id;
144   };
145   std::vector<Worker> workers;
146   for (const auto& job : jobs_) {
147     Worker* worker = nullptr;
148     for (Worker& cur : workers) {
149       // Pick the worker which has the greatest end time (busy_until) <= the
150       // job's start time.
151       if (cur.busy_until <= job.start_ms) {
152         if (!worker || worker->busy_until < cur.busy_until)
153           worker = &cur;
154       }
155     }
156     if (worker) {
157       // Update the worker's end time with the newly assigned job.
158       worker->busy_until = job.end_ms;
159     } else {
160       static constexpr uint32_t kSyntheticNinjaPid = 1;
161 
162       // All workers are busy, allocate a new one.
163       uint32_t worker_id = static_cast<uint32_t>(workers.size()) + 1;
164       ctx_->process_tracker->SetProcessNameIfUnset(
165           ctx_->process_tracker->GetOrCreateProcess(kSyntheticNinjaPid),
166           ctx_->storage->InternString("Build"));
167       auto utid =
168           ctx_->process_tracker->UpdateThread(worker_id, kSyntheticNinjaPid);
169 
170       base::StackString<32> name("Worker");
171       StringId name_id = ctx_->storage->InternString(name.string_view());
172       ctx_->process_tracker->UpdateThreadNameByUtid(utid, name_id,
173                                                     ThreadNamePriority::kOther);
174       TrackId track_id = ctx_->track_tracker->InternThreadTrack(utid);
175       workers.emplace_back(Worker{/*busy_until=*/job.end_ms, track_id});
176       worker = &workers.back();
177     }
178 
179     static constexpr int64_t kMsToNs = 1000ul * 1000;
180     const int64_t start_ns = job.start_ms * kMsToNs;
181     const int64_t dur_ns = (job.end_ms - job.start_ms) * kMsToNs;
182     StringId name_id = ctx_->storage->InternString(base::StringView(job.names));
183     ctx_->slice_tracker->Scoped(start_ns, worker->track_id, StringId::Null(),
184                                 name_id, dur_ns);
185   }
186   return base::OkStatus();
187 }
188 
189 }  // namespace perfetto::trace_processor
190