TabletsProcDir.java

// 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.

package org.apache.doris.common.proc;

import org.apache.doris.catalog.DiskInfo;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.MaterializedIndex;
import org.apache.doris.catalog.Replica;
import org.apache.doris.catalog.Table;
import org.apache.doris.catalog.Tablet;
import org.apache.doris.cloud.catalog.CloudReplica;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.Config;
import org.apache.doris.common.FeConstants;
import org.apache.doris.common.util.ListComparator;
import org.apache.doris.common.util.NetUtils;
import org.apache.doris.common.util.TimeUtils;
import org.apache.doris.statistics.query.QueryStatsUtil;
import org.apache.doris.system.Backend;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/*
 * SHOW PROC /dbs/dbId/tableId/partitions/partitionId/indexId
 * show tablets' detail info within an index
 */
public class TabletsProcDir implements ProcDirInterface {
    public static final ImmutableList<String> TITLE_NAMES;

    static {
        ImmutableList.Builder<String> builder = new ImmutableList.Builder<String>()
                .add("TabletId").add("ReplicaId").add("BackendId").add("SchemaHash").add("Version")
                .add("LstSuccessVersion").add("LstFailedVersion").add("LstFailedTime")
                .add("LocalDataSize").add("RemoteDataSize").add("RowCount").add("State")
                .add("LstConsistencyCheckTime").add("CheckVersion")
                .add("VisibleVersionCount").add("VersionCount").add("QueryHits").add("PathHash").add("Path")
                .add("MetaUrl").add("CompactionStatus")
                .add("CooldownReplicaId").add("CooldownMetaId");

        if (Config.isCloudMode()) {
            builder.add("PrimaryBackendId");
        }

        TITLE_NAMES = builder.build();
    }

    private Table table;
    private MaterializedIndex index;

    public TabletsProcDir(Table table, MaterializedIndex index) {
        this.table = table;
        this.index = index;
    }

    public List<List<Comparable>> fetchComparableResult(long version, long backendId, Replica.ReplicaState state)
            throws AnalysisException {
        Preconditions.checkNotNull(table);
        Preconditions.checkNotNull(index);
        ImmutableMap<Long, Backend> backendMap = Env.getCurrentSystemInfo().getAllBackendsByAllCluster();

        List<List<Comparable>> tabletInfos = new ArrayList<List<Comparable>>();
        Map<Long, String> pathHashToRoot = new HashMap<>();
        for (Backend be : backendMap.values()) {
            for (DiskInfo diskInfo : be.getDisks().values()) {
                pathHashToRoot.put(diskInfo.getPathHash(), diskInfo.getRootPath());
            }
        }
        table.readLock();
        try {
            Map<Long, Long> replicaIdToQueryHits = new HashMap<>();
            if (Config.enable_query_hit_stats) {
                List<Long> replicaIds = new ArrayList<Long>();
                for (Tablet tablet : index.getTablets()) {
                    for (Replica replica : tablet.getReplicas()) {
                        replicaIds.add(replica.getId());
                    }
                }
                replicaIdToQueryHits = QueryStatsUtil.getMergedReplicasStats(replicaIds);
            }

            // get infos
            for (Tablet tablet : index.getTablets()) {
                long tabletId = tablet.getId();
                if (tablet.getReplicas().size() == 0) {
                    List<Comparable> tabletInfo = new ArrayList<Comparable>();
                    tabletInfo.add(tabletId);
                    tabletInfo.add(-1); // replica id
                    tabletInfo.add(-1); // backend id
                    tabletInfo.add(-1); // schema hash
                    tabletInfo.add(FeConstants.null_string); // host name
                    tabletInfo.add(-1); // version
                    tabletInfo.add(-1); // version hash
                    tabletInfo.add(-1); // lst success version
                    tabletInfo.add(-1); // lst success version hash
                    tabletInfo.add(-1); // lst failed version
                    tabletInfo.add(-1); // lst failed version hash
                    tabletInfo.add(-1); // lst failed time
                    tabletInfo.add(-1); // data size
                    tabletInfo.add(-1); // remote data size
                    tabletInfo.add(-1); // row count
                    tabletInfo.add(FeConstants.null_string); // state
                    tabletInfo.add(-1); // lst consistency check time
                    tabletInfo.add(-1); // check version
                    tabletInfo.add(-1); // check version hash
                    tabletInfo.add(-1); // visible version count
                    tabletInfo.add(-1); // total version count
                    tabletInfo.add(0L); // query hits
                    tabletInfo.add(-1); // path hash
                    tabletInfo.add(FeConstants.null_string); // path
                    tabletInfo.add(FeConstants.null_string); // meta url
                    tabletInfo.add(FeConstants.null_string); // compaction status
                    tabletInfo.add(-1); // cooldown replica id
                    tabletInfo.add(""); // cooldown meta id
                    if (Config.isCloudMode()) {
                        tabletInfo.add(-1L); // primary backend id
                    }

                    tabletInfos.add(tabletInfo);
                } else {
                    for (Replica replica : tablet.getReplicas()) {
                        long beId = replica.getBackendIdWithoutException();
                        if ((version > -1 && replica.getVersion() != version)
                                || (backendId > -1 && beId != backendId)
                                || (state != null && replica.getState() != state)) {
                            continue;
                        }
                        List<Comparable> tabletInfo = new ArrayList<Comparable>();
                        // tabletId -- replicaId -- backendId -- version -- dataSize -- rowCount -- state
                        tabletInfo.add(tabletId);
                        tabletInfo.add(replica.getId());
                        tabletInfo.add(beId);
                        tabletInfo.add(replica.getSchemaHash());
                        tabletInfo.add(replica.getVersion());
                        tabletInfo.add(replica.getLastSuccessVersion());
                        tabletInfo.add(replica.getLastFailedVersion());
                        tabletInfo.add(TimeUtils.longToTimeString(replica.getLastFailedTimestamp()));
                        tabletInfo.add(replica.getDataSize());
                        tabletInfo.add(replica.getRemoteDataSize());
                        tabletInfo.add(replica.getRowCount());
                        tabletInfo.add(replica.getState());

                        tabletInfo.add(TimeUtils.longToTimeString(tablet.getLastCheckTime()));
                        tabletInfo.add(tablet.getCheckedVersion());
                        tabletInfo.add(replica.getVisibleVersionCount());
                        tabletInfo.add(replica.getTotalVersionCount());
                        tabletInfo.add(replicaIdToQueryHits.getOrDefault(replica.getId(), 0L));
                        tabletInfo.add(replica.getPathHash());
                        tabletInfo.add(pathHashToRoot.getOrDefault(replica.getPathHash(), ""));
                        Backend be = backendMap.get(beId);
                        String host = (be == null ? Backend.DUMMY_IP : be.getHost());
                        int port = (be == null ? 0 : be.getHttpPort());
                        String hostPort = NetUtils.getHostPortInAccessibleFormat(host, port);
                        String metaUrl = String.format("http://" + hostPort + "/api/meta/header/%d", tabletId);
                        tabletInfo.add(metaUrl);
                        String compactionUrl = String.format(
                                "http://" + hostPort + "/api/compaction/show?tablet_id=%d", tabletId);
                        tabletInfo.add(compactionUrl);
                        tabletInfo.add(tablet.getCooldownConf().first);
                        if (replica.getCooldownMetaId() == null) {
                            tabletInfo.add("");
                        } else {
                            tabletInfo.add(replica.getCooldownMetaId().toString());
                        }
                        if (Config.isCloudMode()) {
                            tabletInfo.add(((CloudReplica) replica).getPrimaryBackendId());
                        }
                        tabletInfos.add(tabletInfo);
                    }
                }
            }
        } finally {
            table.readUnlock();
        }
        return tabletInfos;
    }

    private List<List<Comparable>> fetchComparableResult() throws AnalysisException {
        return fetchComparableResult(-1, -1, null);
    }

    @Override
    public ProcResult fetchResult() throws AnalysisException {
        List<List<Comparable>> tabletInfos = fetchComparableResult();
        // sort by tabletId, replicaId
        ListComparator<List<Comparable>> comparator = new ListComparator<List<Comparable>>(0, 1);
        Collections.sort(tabletInfos, comparator);

        // set result
        BaseProcResult result = new BaseProcResult();
        result.setNames(TITLE_NAMES);

        for (int i = 0; i < tabletInfos.size(); i++) {
            List<Comparable> info = tabletInfos.get(i);
            List<String> row = new ArrayList<String>(info.size());
            for (int j = 0; j < info.size(); j++) {
                row.add(info.get(j).toString());
            }
            result.addRow(row);
        }
        return result;
    }

    @Override
    public boolean register(String name, ProcNodeInterface node) {
        return false;
    }

    @Override
    public ProcNodeInterface lookup(String tabletIdStr) throws AnalysisException {
        Preconditions.checkNotNull(table);
        Preconditions.checkNotNull(index);

        long tabletId = -1L;
        try {
            tabletId = Long.valueOf(tabletIdStr);
        } catch (NumberFormatException e) {
            throw new AnalysisException("Invalid tablet id format: " + tabletIdStr);
        }

        Tablet tablet = index.getTablet(tabletId);
        if (tablet == null) {
            throw new AnalysisException("Tablet[" + tabletId + "] does not exist");
        }

        List<Replica> replicas = tablet.getReplicas();
        return new ReplicasProcNode(tabletId, replicas);
    }

    public static int analyzeColumn(String columnName) throws AnalysisException {
        for (String title : TITLE_NAMES) {
            if (title.equalsIgnoreCase(columnName)) {
                return TITLE_NAMES.indexOf(title);
            }
        }

        throw new AnalysisException("Title name[" + columnName + "] does not exist");
    }
}