All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.search.profile.InternalProfileTree Maven / Gradle / Ivy

There is a newer version: 8.14.1
Show newest version
/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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.
 */

package org.elasticsearch.search.profile;

import org.apache.lucene.search.Query;

import java.util.*;
import java.util.concurrent.LinkedBlockingDeque;

/**
 * This class tracks the dependency tree for queries (scoring and rewriting) and
 * generates {@link ProfileBreakdown} for each node in the tree.  It also finalizes the tree
 * and returns a list of {@link ProfileResult} that can be serialized back to the client
 */
final class InternalProfileTree {

    private ArrayList timings;

    /** Maps the Query to it's list of children.  This is basically the dependency tree */
    private ArrayList> tree;

    /** A list of the original queries, keyed by index position */
    private ArrayList queries;

    /** A list of top-level "roots".  Each root can have its own tree of profiles */
    private ArrayList roots;

    /** Rewrite time */
    private long rewriteTime;
    private long rewriteScratch;

    /** A temporary stack used to record where we are in the dependency tree.  Only used by scoring queries */
    private Deque stack;

    private int currentToken = 0;

    public InternalProfileTree() {
        timings = new ArrayList<>(10);
        stack = new LinkedBlockingDeque<>(10);
        tree = new ArrayList<>(10);
        queries = new ArrayList<>(10);
        roots = new ArrayList<>(10);
    }

    /**
     * Returns a {@link ProfileBreakdown} for a scoring query.  Scoring queries (e.g. those
     * that are past the rewrite phase and are now being wrapped by createWeight() ) follow
     * a recursive progression.  We can track the dependency tree by a simple stack
     *
     * The only hiccup is that the first scoring query will be identical to the last rewritten
     * query, so we need to take special care to fix that
     *
     * @param query The scoring query we wish to profile
     * @return      A ProfileBreakdown for this query
     */
    public ProfileBreakdown getQueryBreakdown(Query query) {
        int token = currentToken;

        boolean stackEmpty = stack.isEmpty();

        // If the stack is empty, we are a new root query
        if (stackEmpty) {

            // We couldn't find a rewritten query to attach to, so just add it as a
            // top-level root. This is just a precaution: it really shouldn't happen.
            // We would only get here if a top-level query that never rewrites for some reason.
            roots.add(token);

            // Increment the token since we are adding a new node, but notably, do not
            // updateParent() because this was added as a root
            currentToken += 1;
            stack.add(token);

            return addDependencyNode(query, token);
        }

        updateParent(token);

        // Increment the token since we are adding a new node
        currentToken += 1;
        stack.add(token);

        return addDependencyNode(query, token);
    }

    /**
     * Begin timing a query for a specific Timing context
     */
    public void startRewriteTime() {
        assert rewriteScratch == 0;
        rewriteScratch = System.nanoTime();
    }

    /**
     * Halt the timing process and add the elapsed rewriting time.
     * startRewriteTime() must be called for a particular context prior to calling
     * stopAndAddRewriteTime(), otherwise the elapsed time will be negative and
     * nonsensical
     *
     * @return          The elapsed time
     */
    public long stopAndAddRewriteTime() {
        long time = Math.max(1, System.nanoTime() - rewriteScratch);
        rewriteTime += time;
        rewriteScratch = 0;
        return time;
    }

    /**
     * Helper method to add a new node to the dependency tree.
     *
     * Initializes a new list in the dependency tree, saves the query and
     * generates a new {@link ProfileBreakdown} to track the timings
     * of this query
     *
     * @param query             The query to profile
     * @param token             The assigned token for this query
     * @return                  A ProfileBreakdown to profile this query
     */
    private ProfileBreakdown addDependencyNode(Query query, int token) {

        // Add a new slot in the dependency tree
        tree.add(new ArrayList(5));

        // Save our query for lookup later
        queries.add(query);

        ProfileBreakdown queryTimings = new ProfileBreakdown();
        timings.add(token, queryTimings);
        return queryTimings;
    }

    /**
     * Removes the last (e.g. most recent) value on the stack
     */
    public void pollLast() {
        stack.pollLast();
    }

    /**
     * After the query has been run and profiled, we need to merge the flat timing map
     * with the dependency graph to build a data structure that mirrors the original
     * query tree
     *
     * @return a hierarchical representation of the profiled query tree
     */
    public List getQueryTree() {
        ArrayList results = new ArrayList<>(5);
        for (Integer root : roots) {
            results.add(doGetQueryTree(root));
        }
        return results;
    }

    /**
     * Recursive helper to finalize a node in the dependency tree
     * @param token  The node we are currently finalizing
     * @return       A hierarchical representation of the tree inclusive of children at this level
     */
    private ProfileResult doGetQueryTree(int token) {
        Query query = queries.get(token);
        ProfileBreakdown breakdown = timings.get(token);
        Map timings = breakdown.toTimingMap();
        List children = tree.get(token);
        List childrenProfileResults = Collections.emptyList();

        if (children != null) {
            childrenProfileResults = new ArrayList<>(children.size());
            for (Integer child : children) {
                ProfileResult childNode = doGetQueryTree(child);
                childrenProfileResults.add(childNode);
            }
        }

        // TODO this would be better done bottom-up instead of top-down to avoid
        // calculating the same times over and over...but worth the effort?
        long nodeTime = getNodeTime(timings, childrenProfileResults);
        String queryDescription = query.getClass().getSimpleName();
        String luceneName = query.toString();
        return new ProfileResult(queryDescription, luceneName, timings, childrenProfileResults, nodeTime);
    }

    public long getRewriteTime() {
        return rewriteTime;
    }

    /**
     * Internal helper to add a child to the current parent node
     *
     * @param childToken The child to add to the current parent
     */
    private void updateParent(int childToken) {
        Integer parent = stack.peekLast();
        ArrayList parentNode = tree.get(parent);
        parentNode.add(childToken);
        tree.set(parent, parentNode);
    }

    /**
     * Internal helper to calculate the time of a node, inclusive of children
     *
     * @param timings   A map of breakdown timing for the node
     * @param children  All children profile results at this node
     * @return          The total time at this node, inclusive of children
     */
    private static long getNodeTime(Map timings, List children) {
        long nodeTime = 0;
        for (long time : timings.values()) {
            nodeTime += time;
        }

        // Then add up our children
        for (ProfileResult child : children) {
            nodeTime += getNodeTime(child.getTimeBreakdown(), child.getProfiledChildren());
        }
        return nodeTime;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy