| /* | |
| * Copyright (C) 2026 The Android Open Source Project | |
| * | |
| * 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. | |
| */ | |
| #include "src/trace_processor/shell/sql_packages.h" | |
| #include <string> | |
| #include <string_view> | |
| #include <vector> | |
| #include "perfetto/base/logging.h" | |
| #include "perfetto/base/status.h" | |
| #include "perfetto/ext/base/file_utils.h" | |
| #include "perfetto/ext/base/scoped_mmap.h" | |
| #include "perfetto/ext/base/status_macros.h" | |
| #include "perfetto/ext/base/string_utils.h" | |
| #include "perfetto/trace_processor/trace_blob.h" | |
| #include "perfetto/trace_processor/trace_processor.h" | |
| #include "src/trace_processor/util/sql_modules.h" | |
| namespace perfetto::trace_processor { | |
| std::string ParsePackagePath(const std::string& arg, std::string* out_package) { | |
| size_t at_pos = arg.rfind('@'); | |
| if (at_pos != std::string::npos) { | |
| *out_package = arg.substr(at_pos + 1); | |
| return arg.substr(0, at_pos); | |
| } | |
| *out_package = ""; | |
| return arg; | |
| } | |
| base::Status IncludeSqlPackage(TraceProcessor* trace_processor, | |
| const std::string& path_arg, | |
| bool allow_override) { | |
| std::string explicit_package; | |
| std::string root = ParsePackagePath(path_arg, &explicit_package); | |
| // Remove trailing slash | |
| if (!root.empty() && root.back() == '/') | |
| root.resize(root.length() - 1); | |
| if (!base::FileExists(root)) | |
| return base::ErrStatus("Directory %s does not exist.", root.c_str()); | |
| // Get package name: use explicit package if provided, otherwise dirname | |
| std::string package_name; | |
| if (!explicit_package.empty()) { | |
| package_name = explicit_package; | |
| } else { | |
| size_t last_slash = root.rfind('/'); | |
| if (last_slash == std::string::npos) { | |
| return base::ErrStatus("Package path must point to a directory: %s", | |
| root.c_str()); | |
| } | |
| package_name = root.substr(last_slash + 1); | |
| } | |
| std::vector<std::string> paths; | |
| RETURN_IF_ERROR(base::ListFilesRecursive(root, paths)); | |
| sql_modules::NameToPackage modules; | |
| for (const auto& path : paths) { | |
| if (base::GetFileExtension(path) != ".sql") { | |
| continue; | |
| } | |
| std::string path_no_extension = path.substr(0, path.rfind('.')); | |
| if (path_no_extension.find('.') != std::string_view::npos) { | |
| PERFETTO_ELOG("Skipping module %s as it contains a dot in its path.", | |
| path_no_extension.c_str()); | |
| continue; | |
| } | |
| std::string filename = root + "/" + path; | |
| std::string file_contents; | |
| if (!base::ReadFile(filename, &file_contents)) { | |
| return base::ErrStatus("Cannot read file %s", filename.c_str()); | |
| } | |
| std::string import_key = | |
| package_name + "." + sql_modules::GetIncludeKey(path); | |
| modules.Insert(package_name, {}) | |
| .first->push_back({import_key, file_contents}); | |
| } | |
| for (auto module_it = modules.GetIterator(); module_it; ++module_it) { | |
| RETURN_IF_ERROR(trace_processor->RegisterSqlPackage( | |
| {/*name=*/module_it.key(), | |
| /*files=*/module_it.value(), | |
| /*allow_override=*/allow_override})); | |
| } | |
| return base::OkStatus(); | |
| } | |
| base::Status LoadOverridenStdlib(TraceProcessor* trace_processor, | |
| std::string root) { | |
| // Remove trailing slash | |
| if (root.back() == '/') { | |
| root.resize(root.length() - 1); | |
| } | |
| if (!base::FileExists(root)) { | |
| return base::ErrStatus("Directory '%s' does not exist.", root.c_str()); | |
| } | |
| std::vector<std::string> paths; | |
| RETURN_IF_ERROR(base::ListFilesRecursive(root, paths)); | |
| sql_modules::NameToPackage packages; | |
| for (const auto& path : paths) { | |
| if (base::GetFileExtension(path) != ".sql") { | |
| continue; | |
| } | |
| std::string filename = root + "/" + path; | |
| std::string module_file; | |
| if (!base::ReadFile(filename, &module_file)) { | |
| return base::ErrStatus("Cannot read file '%s'", filename.c_str()); | |
| } | |
| std::string module_name = sql_modules::GetIncludeKey(path); | |
| std::string package_name = sql_modules::GetPackageName(module_name); | |
| packages.Insert(package_name, {}) | |
| .first->push_back({module_name, module_file}); | |
| } | |
| for (auto package = packages.GetIterator(); package; ++package) { | |
| trace_processor->RegisterSqlPackage({/*name=*/package.key(), | |
| /*files=*/package.value(), | |
| /*allow_override=*/true}); | |
| } | |
| return base::OkStatus(); | |
| } | |
| base::Status RegisterAllFilesInFolder(const std::string& path, | |
| TraceProcessor& tp) { | |
| std::vector<std::string> files; | |
| RETURN_IF_ERROR(base::ListFilesRecursive(path, files)); | |
| for (const std::string& file : files) { | |
| std::string file_full_path = path + "/" + file; | |
| base::ScopedMmap mmap = base::ReadMmapWholeFile(file_full_path); | |
| if (!mmap.IsValid()) { | |
| return base::ErrStatus("Failed to mmap file: %s", file_full_path.c_str()); | |
| } | |
| RETURN_IF_ERROR(tp.RegisterFileContent( | |
| file_full_path, TraceBlob::FromMmap(std::move(mmap)))); | |
| } | |
| return base::OkStatus(); | |
| } | |
| } // namespace perfetto::trace_processor |