-
Notifications
You must be signed in to change notification settings - Fork 690
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SOLR-17141 branch_9x: Create CpuQueryLimit implementation (#2287)
* SOLR-17141: Create CpuQueryLimit implementation (#2244) * Refactor to fix ThreadStats / ThreadCpuTimer nesting and use it in CpuQueryTimeLimit. * Rename classes to better reflect the type of limit.
- Loading branch information
Showing
10 changed files
with
679 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
solr/core/src/java/org/apache/solr/search/CpuAllowedLimit.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/* | ||
* 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.search; | ||
|
||
import com.google.common.annotations.VisibleForTesting; | ||
import java.lang.invoke.MethodHandles; | ||
import java.util.concurrent.TimeUnit; | ||
import org.apache.lucene.index.QueryTimeout; | ||
import org.apache.solr.common.params.CommonParams; | ||
import org.apache.solr.request.SolrQueryRequest; | ||
import org.apache.solr.request.SolrRequestInfo; | ||
import org.apache.solr.util.ThreadCpuTimer; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* Enforces a CPU-time based timeout on a given SolrQueryRequest, as specified by the {@code | ||
* cpuAllowed} query parameter. | ||
*/ | ||
public class CpuAllowedLimit implements QueryTimeout { | ||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); | ||
|
||
private final long limitAtNs; | ||
private final ThreadCpuTimer threadCpuTimer; | ||
|
||
/** | ||
* Create an object to represent a CPU time limit for the current request. NOTE: this | ||
* implementation will attempt to obtain an existing thread CPU time monitor, created when {@link | ||
* SolrRequestInfo#getThreadCpuTimer()} is initialized. | ||
* | ||
* @param req solr request with a {@code cpuAllowed} parameter | ||
*/ | ||
public CpuAllowedLimit(SolrQueryRequest req) { | ||
if (!ThreadCpuTimer.isSupported()) { | ||
throw new IllegalArgumentException("Thread CPU time monitoring is not available."); | ||
} | ||
SolrRequestInfo solrRequestInfo = SolrRequestInfo.getRequestInfo(); | ||
threadCpuTimer = | ||
solrRequestInfo != null ? solrRequestInfo.getThreadCpuTimer() : new ThreadCpuTimer(); | ||
long reqCpuLimit = req.getParams().getLong(CommonParams.CPU_ALLOWED, -1L); | ||
|
||
if (reqCpuLimit <= 0L) { | ||
throw new IllegalArgumentException( | ||
"Check for limit with hasCpuLimit(req) before creating a CpuAllowedLimit"); | ||
} | ||
// calculate when the time limit is reached, account for the time already spent | ||
limitAtNs = | ||
threadCpuTimer.getStartCpuTimeNs() | ||
+ TimeUnit.NANOSECONDS.convert(reqCpuLimit, TimeUnit.MILLISECONDS); | ||
} | ||
|
||
@VisibleForTesting | ||
CpuAllowedLimit(long limitMs) { | ||
this.threadCpuTimer = new ThreadCpuTimer(); | ||
limitAtNs = | ||
threadCpuTimer.getCurrentCpuTimeNs() | ||
+ TimeUnit.NANOSECONDS.convert(limitMs, TimeUnit.MILLISECONDS); | ||
} | ||
|
||
/** Return true if the current request has a parameter with a valid value of the limit. */ | ||
static boolean hasCpuLimit(SolrQueryRequest req) { | ||
return req.getParams().getLong(CommonParams.CPU_ALLOWED, -1L) > 0L; | ||
} | ||
|
||
/** Return true if a max limit value is set and the current usage has exceeded the limit. */ | ||
@Override | ||
public boolean shouldExit() { | ||
return limitAtNs - threadCpuTimer.getCurrentCpuTimeNs() < 0L; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
solr/core/src/java/org/apache/solr/util/ThreadCpuTimer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
/* | ||
* 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.util; | ||
|
||
import java.lang.invoke.MethodHandles; | ||
import java.lang.management.ManagementFactory; | ||
import java.lang.management.ThreadMXBean; | ||
import java.util.Optional; | ||
import java.util.concurrent.TimeUnit; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* Allows tracking information about the current thread using the JVM's built-in management bean | ||
* {@link java.lang.management.ThreadMXBean}. | ||
* | ||
* <p>Calling code should create an instance of this class when starting the operation, and then can | ||
* get the {@link #getCpuTimeMs()} at any time thereafter. | ||
*/ | ||
public class ThreadCpuTimer { | ||
private static final long UNSUPPORTED = -1; | ||
public static final String CPU_TIME = "cpuTime"; | ||
public static final String LOCAL_CPU_TIME = "localCpuTime"; | ||
public static final String ENABLE_CPU_TIME = "solr.log.cputime"; | ||
|
||
private static ThreadMXBean THREAD_MX_BEAN; | ||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); | ||
|
||
static { | ||
try { | ||
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); | ||
if (!threadBean.isThreadCpuTimeEnabled()) { | ||
threadBean.setThreadCpuTimeEnabled(true); | ||
} | ||
THREAD_MX_BEAN = threadBean; | ||
} catch (UnsupportedOperationException | SecurityException e) { | ||
THREAD_MX_BEAN = null; | ||
log.info("Thread CPU time monitoring is not available."); | ||
} | ||
} | ||
|
||
private final long startCpuTimeNanos; | ||
|
||
/** | ||
* Create an instance to track the current thread's usage of CPU. The usage information can later | ||
* be retrieved by any thread by calling {@link #getCpuTimeMs()}. | ||
*/ | ||
public ThreadCpuTimer() { | ||
if (THREAD_MX_BEAN != null) { | ||
this.startCpuTimeNanos = THREAD_MX_BEAN.getCurrentThreadCpuTime(); | ||
} else { | ||
this.startCpuTimeNanos = UNSUPPORTED; | ||
} | ||
} | ||
|
||
public static boolean isSupported() { | ||
return THREAD_MX_BEAN != null; | ||
} | ||
|
||
/** | ||
* Return the initial value of CPU time for this thread when this instance was first created. | ||
* NOTE: absolute value returned by this method has no meaning by itself, it should only be used | ||
* when comparing elapsed time between this value and {@link #getCurrentCpuTimeNs()}. | ||
* | ||
* @return current value, or {@link #UNSUPPORTED} if not supported. | ||
*/ | ||
public long getStartCpuTimeNs() { | ||
return startCpuTimeNanos; | ||
} | ||
|
||
/** | ||
* Return current value of CPU time for this thread. | ||
* | ||
* @return current value, or {@link #UNSUPPORTED} if not supported. | ||
*/ | ||
public long getCurrentCpuTimeNs() { | ||
if (THREAD_MX_BEAN != null) { | ||
return this.startCpuTimeNanos != UNSUPPORTED | ||
? THREAD_MX_BEAN.getCurrentThreadCpuTime() - this.startCpuTimeNanos | ||
: UNSUPPORTED; | ||
} else { | ||
return UNSUPPORTED; | ||
} | ||
} | ||
|
||
/** | ||
* Get the CPU usage information for the thread that created this {@link ThreadCpuTimer}. The | ||
* information will track the thread's cpu since the creation of this {@link ThreadCpuTimer} | ||
* instance, if the VM's cpu tracking is disabled, returned value will be {@link #UNSUPPORTED}. | ||
*/ | ||
public Optional<Long> getCpuTimeMs() { | ||
long cpuTimeNs = getCurrentCpuTimeNs(); | ||
return cpuTimeNs != UNSUPPORTED | ||
? Optional.of(TimeUnit.MILLISECONDS.convert(cpuTimeNs, TimeUnit.NANOSECONDS)) | ||
: Optional.of(UNSUPPORTED); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return getCpuTimeMs().toString(); | ||
} | ||
} |
Oops, something went wrong.