/* * 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.solr.metrics.reporters.solr; import java.io.IOException; import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import org.apache.solr.cloud.CloudDescriptor; import org.apache.solr.common.cloud.ClusterState; import org.apache.solr.common.cloud.DocCollection; import org.apache.solr.common.cloud.Replica; import org.apache.solr.core.SolrCore; import org.apache.solr.handler.admin.MetricsCollectorHandler; import org.apache.solr.metrics.SolrMetricManager; import org.apache.solr.metrics.SolrMetricReporter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class reports selected metrics from replicas to shard leader. * <p>The following configuration properties are supported:</p> * <ul> * <li>handler - (optional str) handler path where reports are sent. Default is * {@link MetricsCollectorHandler#HANDLER_PATH}.</li> * <li>period - (optional int) how often reports are sent, in seconds. Default is 60. Setting this * to 0 disables the reporter.</li> * <li>filter - (optional multiple str) regex expression(s) matching selected metrics to be reported.</li> * </ul> * NOTE: this reporter uses predefined "replica" group, and it's always created even if explicit configuration * is missing. Default configuration uses filters defined in {@link #DEFAULT_FILTERS}. * <p>Example configuration:</p> * <pre> * <reporter name="test" group="replica"> * <int name="period">11</int> * <str name="filter">UPDATE\./update/.*requests</str> * <str name="filter">QUERY\./select.*requests</str> * </reporter> * </pre> */ public class SolrShardReporter extends SolrMetricReporter { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); public static final List<String> DEFAULT_FILTERS = new ArrayList(){{ add("TLOG.*"); add("CORE\\.fs.*"); add("REPLICATION.*"); add("INDEX\\.flush.*"); add("INDEX\\.merge\\.major.*"); add("UPDATE\\./update/.*requests"); add("QUERY\\./select.*requests"); }}; private String handler = MetricsCollectorHandler.HANDLER_PATH; private int period = SolrMetricManager.DEFAULT_CLOUD_REPORTER_PERIOD; private List<String> filters = new ArrayList<>(); private SolrReporter reporter; /** * Create a reporter for metrics managed in a named registry. * * @param metricManager metric manager * @param registryName registry to use, one of registries managed by * {@link SolrMetricManager} */ public SolrShardReporter(SolrMetricManager metricManager, String registryName) { super(metricManager, registryName); } public void setHandler(String handler) { this.handler = handler; } public void setPeriod(int period) { this.period = period; } public void setFilter(List<String> filterConfig) { if (filterConfig == null || filterConfig.isEmpty()) { return; } filters.addAll(filterConfig); } public void setFilter(String filter) { if (filter != null && !filter.isEmpty()) { this.filters.add(filter); } } // for unit tests int getPeriod() { return period; } @Override protected void validate() throws IllegalStateException { if (filters.isEmpty()) { filters = DEFAULT_FILTERS; } // start in inform(...) only when core is available } @Override public void close() throws IOException { if (reporter != null) { reporter.close(); } } public void setCore(SolrCore core) { if (reporter != null) { reporter.close(); } if (!enabled) { log.info("Reporter disabled for registry " + registryName); return; } if (core.getCoreDescriptor().getCloudDescriptor() == null) { // not a cloud core log.warn("Not initializing shard reporter for non-cloud core " + core.getName()); return; } if (period < 1) { // don't start it log.warn("period=" + period + ", not starting shard reporter "); return; } // our id is coreNodeName String id = core.getCoreDescriptor().getCloudDescriptor().getCoreNodeName(); // target registry is the leaderRegistryName String groupId = core.getCoreMetricManager().getLeaderRegistryName(); if (groupId == null) { log.warn("No leaderRegistryName for core " + core + ", not starting the reporter..."); return; } SolrReporter.Report spec = new SolrReporter.Report(groupId, null, registryName, filters); reporter = SolrReporter.Builder.forReports(metricManager, Collections.singletonList(spec)) .convertRatesTo(TimeUnit.SECONDS) .convertDurationsTo(TimeUnit.MILLISECONDS) .withHandler(handler) .withReporterId(id) .setCompact(true) .cloudClient(false) // we want to send reports specifically to a selected leader instance .skipAggregateValues(true) // we don't want to transport details of aggregates .skipHistograms(true) // we don't want to transport histograms .build(core.getCoreContainer().getUpdateShardHandler().getHttpClient(), new LeaderUrlSupplier(core)); reporter.start(period, TimeUnit.SECONDS); } private static class LeaderUrlSupplier implements Supplier<String> { private SolrCore core; LeaderUrlSupplier(SolrCore core) { this.core = core; } @Override public String get() { CloudDescriptor cd = core.getCoreDescriptor().getCloudDescriptor(); if (cd == null) { return null; } ClusterState state = core.getCoreContainer().getZkController().getClusterState(); DocCollection collection = state.getCollection(core.getCoreDescriptor().getCollectionName()); Replica replica = collection.getLeader(core.getCoreDescriptor().getCloudDescriptor().getShardId()); if (replica == null) { log.warn("No leader for " + collection.getName() + "/" + core.getCoreDescriptor().getCloudDescriptor().getShardId()); return null; } String baseUrl = replica.getStr("base_url"); if (baseUrl == null) { log.warn("No base_url for replica " + replica); } return baseUrl; } } }