blob: de6479b3934c59656e136afd472044e1910d8113 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.
#pragma once
#include <cstddef>
#include <cstdint>
#include <functional>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <rapidjson/document.h>
#include "common/status.h"
namespace impala {
typedef rapidjson::Document ParsedProfile;
/// Provides read-only, tool-oriented accessors over a parsed query profile JSON tree.
///
/// Purpose:
/// - Encapsulates query-profile traversal/normalization logic behind stable helper
/// methods used by query profile tools (summary, fragments, nodes, timelines, etc.).
/// - Handles profile-shape differences (for example duplicated keys collected as arrays)
/// and query scoping decisions internally, so callers can request high-level sections.
///
/// Usage:
/// - Preferred path: call CreateQueryProfileToolExecutorForProfile() with a profile
/// JSON DOM value, then invoke tools through the returned QueryProfileToolExecutor.
/// - Direct path: construct QueryProfileToolAccessor with a ParsedProfile when the
/// profile is already parsed and validated by the caller.
///
/// Input format:
/// - Accepts profile data normalized by ParseProfile(). The source JSON must be either:
/// 1) a root object containing "contents" (object) whose "profile_name" is a string, or
/// 2) a root object with top-level "profile_name" (string).
/// - Profile sections are expected to be nested JSON objects/arrays built from the
/// profile text export.
/// - High-level accepted input shape:
/// {
/// "contents": {
/// "profile_name": "Query (id=...)",
/// "child_profiles": [ ... ],
/// "info_strings": [ {"key": "...", "value": ...}, ... ]
/// }
/// }
///
/// Output behavior:
/// - Accessor methods return JSON values (mostly objects or arrays depending on method).
/// - Methods that cannot find a section generally return an empty JSON object/array.
/// - Some lookup methods (for example fragment/node lookups) return an object containing
/// an "error" field when a specific requested item is not found.
class QueryProfileToolAccessor {
public:
explicit QueryProfileToolAccessor(ParsedProfile parsed);
QueryProfileToolAccessor(const QueryProfileToolAccessor&) = delete;
QueryProfileToolAccessor& operator=(const QueryProfileToolAccessor&) = delete;
QueryProfileToolAccessor(QueryProfileToolAccessor&&) = delete;
QueryProfileToolAccessor& operator=(QueryProfileToolAccessor&&) = delete;
// Returns the selected query's "Summary" section as-is.
// Example: {"Query State":"FINISHED","Plan":"..."}.
rapidjson::Value GetSummary(rapidjson::Document::AllocatorType& alloc) const;
// Returns execution event timings (labels -> "absolute (delta)").
// Example: {"Planning":"2.3ms (2.3ms)","Submit for admission":"5.1ms (2.8ms)"}.
rapidjson::Value GetTimeline(rapidjson::Document::AllocatorType& alloc) const;
// Returns compilation event timings using the same shape as GetTimeline().
rapidjson::Value GetCompilation(rapidjson::Document::AllocatorType& alloc) const;
// Returns the selected "Execution Profile..." subtree.
rapidjson::Value GetExecutionProfile(rapidjson::Document::AllocatorType& alloc) const;
// Returns one entry per selected fragment with derived metadata.
// Example row: {"fragment_id":"F00","full_key":"Fragment F00","instance_count":12}.
rapidjson::Value GetFragmentsOverview(rapidjson::Document::AllocatorType& alloc) const;
// Returns one averaged fragment object for a fragment id (e.g. "F00" or "00").
rapidjson::Value GetFragment(std::string_view fragment_id,
rapidjson::Document::AllocatorType& alloc) const;
// Returns all matching fragment variants for a fragment id.
rapidjson::Value GetAllFragments(
std::string_view fragment_id, rapidjson::Document::AllocatorType& alloc) const;
// Returns only instance children from the selected fragment.
// Example keys: "Instance 1234...","Instance 5678...".
rapidjson::Value GetFragmentInstances(
std::string_view fragment_id, rapidjson::Document::AllocatorType& alloc) const;
// Returns one best-matching operator section, optionally scoped to a fragment.
// Example node_id: "45", "HDFS_SCAN_NODE", "HDFS_SCAN_NODE (id=45)".
rapidjson::Value GetOperator(std::string_view node_id, std::string_view fragment_id,
rapidjson::Document::AllocatorType& alloc) const;
// Returns one best-matching averaged operator section for the node id.
rapidjson::Value GetAveragedOperator(
std::string_view node_id, rapidjson::Document::AllocatorType& alloc) const;
// Returns all per-instance operator sections for the node id, including fragment
// and instance(host) context.
rapidjson::Value GetAllOperators(std::string_view node_id, std::string_view fragment_id,
rapidjson::Document::AllocatorType& alloc) const;
// Returns one specific per-instance operator section for the node id.
// If instance_id is empty, returns the first deterministic match.
rapidjson::Value GetSpecificOperator(std::string_view node_id,
std::string_view fragment_id, std::string_view instance_id,
rapidjson::Document::AllocatorType& alloc) const;
// Returns compact metrics for scan nodes across fragments.
rapidjson::Value GetScanNodesSummary(rapidjson::Document::AllocatorType& alloc) const;
// Returns the "Per Node Profiles" object when present.
rapidjson::Value GetPerNodeProfiles(rapidjson::Document::AllocatorType& alloc) const;
// Parses summary options into {"option":"value"} form.
// Example source: "MT_DOP=4, RUNTIME_FILTER_MODE=GLOBAL".
rapidjson::Value GetQueryOptions(rapidjson::Document::AllocatorType& alloc) const;
// Returns Summary "Tables Queried" as a string array.
// Example: ["db.fact_sales","db.dim_date"].
rapidjson::Value GetTablesQueried(rapidjson::Document::AllocatorType& alloc) const;
// Returns reservation/estimate/coordinator resource fields when available.
rapidjson::Value GetResourceEstimates(rapidjson::Document::AllocatorType& alloc) const;
// Extracts the SQL text under "Analyzed query:" from the Plan field.
std::string GetAnalyzedQuery() const;
private:
using FragmentEntry = std::pair<std::string, const rapidjson::Value*>;
struct FragmentEntryLess {
bool operator()(const FragmentEntry& lhs, const FragmentEntry& rhs) const {
return lhs.first < rhs.first;
}
};
struct FragmentPtrLess {
bool operator()(const FragmentEntry* lhs, const FragmentEntry* rhs) const {
if (lhs->first != rhs->first) return lhs->first < rhs->first;
return lhs < rhs;
}
};
std::string_view GetQueryType(const rapidjson::Value& query_obj) const;
const rapidjson::Value* SelectScopedValue(const rapidjson::Value& value) const;
const rapidjson::Value* GetScopedExecutionProfile() const;
int DictComplexity(const rapidjson::Value& v) const;
const rapidjson::Value* GetQueryData() const;
const rapidjson::Value* GetSummaryPtr() const;
rapidjson::Value BuildEventTimelineSection(
int sequence_idx, rapidjson::Document::AllocatorType& alloc) const;
bool IsFragmentKey(std::string_view key) const;
void GatherFragments(const rapidjson::Value& data,
std::vector<std::pair<std::string, const rapidjson::Value*>>* out) const;
int ScoreNodeKeyMatch(std::string_view key, std::string_view node_id) const;
int ScoreOperatorKeyMatch(std::string_view key, std::string_view node_id) const;
void FindBestOperatorMatch(const rapidjson::Value& data, std::string_view node_id,
std::string_view* best_key, const rapidjson::Value** best_match, int* best_score,
int* best_complexity) const;
const rapidjson::Value* SearchOperator(const rapidjson::Value& data,
std::string_view node_id, std::string_view* matched_key = nullptr) const;
void FindNodesByPattern(const rapidjson::Value& data, std::string_view pattern,
std::vector<std::pair<std::string_view, const rapidjson::Value*>>* out) const;
void FindNodesByPatternNormalized(const rapidjson::Value& data,
const std::string& normalized_pattern,
std::vector<std::pair<std::string_view, const rapidjson::Value*>>* out) const;
double ParseDurationMs(std::string_view value) const;
void AddOperatorRows(std::string_view text, rapidjson::Value* info,
rapidjson::Document::AllocatorType& alloc) const;
// Parsed root profile after ParseProfile() normalization.
// Example top-level key: "Query (id=084f...):".
ParsedProfile parsed_;
// All fragment-like blocks discovered in the scoped execution profile.
// Example key/value:
// key = "Fragment F00 [2]"
// value = {"Instance 8b4...": {...}, "_value":"..." }
std::vector<FragmentEntry> all_fragments_;
// Deduplicated "best" fragment blocks, one representative per fragment id.
// This keeps the most information-dense object when multiple keys map to
// the same id.
std::set<FragmentEntry, FragmentEntryLess> fragments_;
struct StringViewPairHash {
std::size_t operator()(
const std::pair<std::string_view, std::string_view>& value) const {
return std::hash<std::string_view>()(value.first)
^ (std::hash<std::string_view>()(value.second) << 1);
}
};
// Selected query object key in parsed_.
// Example: "Query (id=084f0b6e6340f5d9:6dd77f6f00000000):".
std::string query_key_;
// Query id extracted from query_key_.
// Example: "084f0b6e6340f5d9:6dd77f6f00000000".
std::string query_id_;
// Cached top-level execution profile key derived from query_id_.
std::string execution_profile_key_;
// Index used when a section appears as an array across query scopes.
// For QUERY statements, this tracks the matching query-type scope index.
int query_type_query_scope_idx_ = 0;
};
using QueryProfileToolExecutor = std::function<Status(
std::string_view tool_name, std::string_view tool_args_json,
rapidjson::Document* tool_output_doc)>;
// Parses one already-materialized profile JSON object and returns a reusable tool
// executor.
//
// If profile_size_limit_bytes is <= 0, a default limit is chosen as:
// min(256 MB, 1% of process_mem_limit). If process_mem_limit is unavailable,
// 256 MB is used.
//
// If profile_size_bytes is 0, the function computes it by serializing
// profile_json, then rejects parsing when the effective size exceeds the
// effective profile size limit.
Status CreateQueryProfileToolExecutorForProfile(
const rapidjson::Value& profile_json, QueryProfileToolExecutor* tool_executor,
int64_t profile_size_limit_bytes = -1, size_t profile_size_bytes = 0);
// Internal helper wrappers exposed for offline unit testing.
namespace test {
double ParseDurationMs(std::string_view value);
} // namespace test
} // namespace impala