blob: 5af640e9d360a30ccbfd0ace69a5362f74a7dcf8 [file] [log] [blame]
/*
* 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.sling.cms.core.models;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
import javax.jcr.query.Row;
import javax.jcr.query.RowIterator;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.osgi.annotation.versioning.ProviderType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ProviderType
@Model(adaptables = SlingHttpServletRequest.class)
public class QueryDebugger {
private static final String MBEAN_NAME = "org.apache.jackrabbit.oak:name=Oak Query Statistics (Extended),type=QueryStats";
private static final Logger log = LoggerFactory.getLogger(QueryDebugger.class);
private final String plan;
private final long duration;
private final long estimatedSize;
private final String exception;
private final List<Resource> results = new ArrayList<>();
private final String statement;
private final List<Map<String, Object>> slowQueries = new ArrayList<>();
private final List<Map<String, Object>> popularQueries = new ArrayList<>();
private final boolean enabled;
@Inject
public QueryDebugger(@Self SlingHttpServletRequest request) {
Optional<String> statementParam = Optional.ofNullable(request.getParameter("statement"));
String language = Optional.ofNullable(request.getParameter("language")).orElse(Query.JCR_SQL2);
int limit = Optional.ofNullable(request.getParameter("sample")).map(s -> Integer.parseInt(s, 10)).orElse(0);
boolean lenabled = false;
long lestimate = 0;
long lduration = -1;
String lplan = null;
String lexception = null;
String lstatement = null;
try {
if (statementParam.isPresent()) {
QueryManager queryManager = request.getResourceResolver().adaptTo(Session.class).getWorkspace()
.getQueryManager();
Query explainQuery = queryManager.createQuery("explain measure " + statementParam.get(), language);
Row row = explainQuery.execute().getRows().nextRow();
lplan = row.getValue("plan").getString();
lstatement = statementParam.get();
if (limit > 0) {
lenabled = true;
long start = System.currentTimeMillis();
Query query = queryManager.createQuery(statementParam.get(), language);
query.setLimit(limit);
QueryResult queryResult = query.execute();
RowIterator rowIterator = queryResult.getRows();
lestimate = rowIterator.getSize();
lduration = System.currentTimeMillis() - start;
while (rowIterator.hasNext()) {
Optional.ofNullable(rowIterator.nextRow())
.map(n -> {
try {
return request.getResourceResolver().getResource(n.getPath());
} catch (RepositoryException e) {
log.warn("Exception getting path from row: {}", n, e);
return null;
}
})
.ifPresent(results::add);
}
}
}
} catch (RepositoryException re) {
lexception = re.toString();
log.warn("Failed to debug query: {}", statementParam, re);
} finally {
this.plan = lplan;
this.duration = lduration;
this.exception = lexception;
this.statement = lstatement;
this.estimatedSize = lestimate;
this.enabled = lenabled;
}
try {
collectMbeanData("PopularQueries", popularQueries);
collectMbeanData("SlowQueries", slowQueries);
} catch (MBeanException | MalformedObjectNameException | InstanceNotFoundException | AttributeNotFoundException
| NullPointerException | ReflectionException e) {
log.warn("Failed to load mBean data", e);
}
}
private void collectMbeanData(String attributeName, List<Map<String, Object>> target)
throws MalformedObjectNameException, NullPointerException, InstanceNotFoundException,
AttributeNotFoundException, ReflectionException, MBeanException {
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName mbeanName = ObjectName.getInstance(MBEAN_NAME);
TabularData data = (TabularData) mBeanServer.getAttribute(mbeanName, attributeName);
data.values().stream().map(CompositeData.class::cast)
.forEach(compositeData -> target.add(compositeData.getCompositeType().keySet().stream()
.collect(Collectors.toMap(k -> k, compositeData::get))));
}
/**
* @return the execution duration or -1
*/
public long getDuration() {
return duration;
}
/**
* @return the estimated size
*/
public long getEstimatedSize() {
return estimatedSize;
}
/**
* @return if execution enabled
*/
public boolean isExecutionEnabled() {
return enabled;
}
/**
* @return the exception
*/
public String getException() {
return exception;
}
/**
* @return the plan
*/
public String getPlan() {
return plan;
}
/**
* @return the results
*/
public List<Resource> getResults() {
return results;
}
/**
* @return the statement
*/
public String getStatement() {
return statement;
}
/**
* @return the slowQueries
*/
public List<Map<String, Object>> getSlowQueries() {
return slowQueries;
}
/**
* @return the popularQueries
*/
public List<Map<String, Object>> getPopularQueries() {
return popularQueries;
}
}