1 /*
2 * Copyright (C) 2024 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/gecko/gecko_trace_tokenizer.h"
18
19 #include <json/value.h>
20 #include <cstddef>
21 #include <cstdint>
22 #include <optional>
23 #include <string>
24 #include <string_view>
25 #include <vector>
26
27 #include "perfetto/base/status.h"
28 #include "perfetto/ext/base/flat_hash_map.h"
29 #include "perfetto/ext/base/string_view.h"
30 #include "perfetto/trace_processor/trace_blob_view.h"
31 #include "protos/perfetto/trace/clock_snapshot.pbzero.h"
32 #include "src/trace_processor/importers/common/mapping_tracker.h"
33 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
34 #include "src/trace_processor/importers/common/virtual_memory_mapping.h"
35 #include "src/trace_processor/importers/gecko/gecko_event.h"
36 #include "src/trace_processor/importers/json/json_utils.h"
37 #include "src/trace_processor/sorter/trace_sorter.h"
38 #include "src/trace_processor/storage/trace_storage.h"
39 #include "src/trace_processor/types/trace_processor_context.h"
40 #include "src/trace_processor/util/status_macros.h"
41
42 namespace perfetto::trace_processor::gecko_importer {
43 namespace {
44
45 struct Callsite {
46 CallsiteId id;
47 uint32_t depth;
48 };
49
50 } // namespace
51
GeckoTraceTokenizer(TraceProcessorContext * ctx)52 GeckoTraceTokenizer::GeckoTraceTokenizer(TraceProcessorContext* ctx)
53 : context_(ctx) {}
54 GeckoTraceTokenizer::~GeckoTraceTokenizer() = default;
55
Parse(TraceBlobView blob)56 base::Status GeckoTraceTokenizer::Parse(TraceBlobView blob) {
57 pending_json_.append(reinterpret_cast<const char*>(blob.data()), blob.size());
58 return base::OkStatus();
59 }
60
NotifyEndOfFile()61 base::Status GeckoTraceTokenizer::NotifyEndOfFile() {
62 std::optional<Json::Value> opt_value =
63 json::ParseJsonString(base::StringView(pending_json_));
64 if (!opt_value) {
65 return base::ErrStatus(
66 "Syntactic error while Gecko trace; please use an external JSON tool "
67 "(e.g. jq) to understand the source of the error.");
68 }
69 context_->clock_tracker->SetTraceTimeClock(
70 protos::pbzero::ClockSnapshot::Clock::MONOTONIC);
71
72 DummyMemoryMapping* dummy_mapping = nullptr;
73 base::FlatHashMap<std::string, DummyMemoryMapping*> mappings;
74
75 const Json::Value& value = *opt_value;
76 std::vector<FrameId> frame_ids;
77 std::vector<Callsite> callsites;
78 for (const auto& t : value["threads"]) {
79 // The trace uses per-thread indices, we reuse the vector for perf reasons
80 // to prevent reallocs on every thread.
81 frame_ids.clear();
82 callsites.clear();
83
84 const auto& strings = t["stringTable"];
85 const auto& frames = t["frameTable"];
86 const auto& frames_schema = frames["schema"];
87 uint32_t location_idx = frames_schema["location"].asUInt();
88 for (const auto& frame : frames["data"]) {
89 base::StringView name = strings[frame[location_idx].asUInt()].asCString();
90
91 constexpr std::string_view kMappingStart = " (in ";
92 size_t mapping_meta_start = name.find(
93 base::StringView(kMappingStart.data(), kMappingStart.size()));
94 if (mapping_meta_start == base::StringView::npos &&
95 name.data()[name.size() - 1] == ')') {
96 if (!dummy_mapping) {
97 dummy_mapping =
98 &context_->mapping_tracker->CreateDummyMapping("gecko");
99 }
100 frame_ids.push_back(
101 dummy_mapping->InternDummyFrame(name, base::StringView()));
102 continue;
103 }
104
105 DummyMemoryMapping* mapping;
106 size_t mapping_start = mapping_meta_start + kMappingStart.size();
107 size_t mapping_end = name.find(')', mapping_start);
108 std::string mapping_name =
109 name.substr(mapping_start, mapping_end - mapping_start).ToStdString();
110 if (auto* mapping_ptr = mappings.Find(mapping_name); mapping_ptr) {
111 mapping = *mapping_ptr;
112 } else {
113 mapping = &context_->mapping_tracker->CreateDummyMapping(mapping_name);
114 mappings.Insert(mapping_name, mapping);
115 }
116 frame_ids.push_back(mapping->InternDummyFrame(
117 name.substr(0, mapping_meta_start), base::StringView()));
118 }
119
120 const auto& stacks = t["stackTable"];
121 const auto& stacks_schema = stacks["schema"];
122 uint32_t prefix_index = stacks_schema["prefix"].asUInt();
123 uint32_t frame_index = stacks_schema["frame"].asUInt();
124 for (const auto& frame : stacks["data"]) {
125 const auto& prefix = frame[prefix_index];
126 std::optional<CallsiteId> prefix_id;
127 uint32_t depth = 0;
128 if (!prefix.isNull()) {
129 const auto& c = callsites[prefix.asUInt()];
130 prefix_id = c.id;
131 depth = c.depth + 1;
132 }
133 CallsiteId cid = context_->stack_profile_tracker->InternCallsite(
134 prefix_id, frame_ids[frame[frame_index].asUInt()], depth);
135 callsites.push_back({cid, depth});
136 }
137
138 const auto& samples = t["samples"];
139 const auto& samples_schema = samples["schema"];
140 uint32_t stack_index = samples_schema["stack"].asUInt();
141 uint32_t time_index = samples_schema["time"].asUInt();
142 bool added_metadata = false;
143 for (const auto& sample : samples["data"]) {
144 uint32_t stack_idx = sample[stack_index].asUInt();
145 auto ts =
146 static_cast<int64_t>(sample[time_index].asDouble() * 1000 * 1000);
147 if (!added_metadata) {
148 context_->sorter->PushGeckoEvent(
149 ts, GeckoEvent{GeckoEvent::ThreadMetadata{
150 t["tid"].asUInt(), t["pid"].asUInt(),
151 context_->storage->InternString(t["name"].asCString())}});
152 added_metadata = true;
153 }
154 ASSIGN_OR_RETURN(
155 int64_t converted,
156 context_->clock_tracker->ToTraceTime(
157 protos::pbzero::ClockSnapshot::Clock::MONOTONIC, ts));
158 context_->sorter->PushGeckoEvent(
159 converted, GeckoEvent{GeckoEvent::StackSample{
160 t["tid"].asUInt(), callsites[stack_idx].id}});
161 }
162 }
163 return base::OkStatus();
164 }
165
166 } // namespace perfetto::trace_processor::gecko_importer
167