blob: 060a2b42bc703f7f2688a66ee2deb3061b52ad1a [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.skywalking.oap.server.core.storage.query;
import com.google.common.base.Strings;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.apache.skywalking.oap.server.core.Const;
import org.apache.skywalking.oap.server.core.analysis.metrics.DataTable;
import org.apache.skywalking.oap.server.core.query.input.Duration;
import org.apache.skywalking.oap.server.core.query.input.MetricsCondition;
import org.apache.skywalking.oap.server.core.query.type.HeatMap;
import org.apache.skywalking.oap.server.core.query.type.IntValues;
import org.apache.skywalking.oap.server.core.query.type.KVInt;
import org.apache.skywalking.oap.server.core.query.type.KeyValue;
import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
import org.apache.skywalking.oap.server.core.storage.DAO;
import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
import org.apache.skywalking.oap.server.library.util.StringUtil;
import static java.util.stream.Collectors.toList;
/**
* Query metrics values in different ways.
*
* @since 8.0.0
*/
public interface IMetricsQueryDAO extends DAO {
int METRICS_VALUES_WITHOUT_ENTITY_LIMIT = 10;
MetricsValues readMetricsValues(MetricsCondition condition,
String valueColumnName,
Duration duration) throws IOException;
List<MetricsValues> readLabeledMetricsValues(MetricsCondition condition,
String valueColumnName,
List<KeyValue> labels,
Duration duration) throws IOException;
/**
* Read metrics values without entity. Used for get the labels' metadata.
*/
List<MetricsValues> readLabeledMetricsValuesWithoutEntity(String metricName,
String valueColumnName,
List<KeyValue> labels,
Duration duration) throws IOException;
HeatMap readHeatMap(MetricsCondition condition, String valueColumnName, Duration duration) throws IOException;
class Util {
/**
* Make sure the order is same as the expected order, add defaultValue if absent.
*/
public static IntValues sortValues(IntValues origin, List<String> expectedOrder, int defaultValue) {
IntValues intValues = new IntValues();
expectedOrder.forEach(id -> {
intValues.addKVInt(origin.findValue(id, defaultValue));
});
return intValues;
}
/**
* Make sure the order is same as the expected order, add defaultValue if absent.
*/
public static List<MetricsValues> sortValues(List<MetricsValues> origin,
List<String> expectedOrder,
int defaultValue) {
for (int i = 0; i < origin.size(); i++) {
final MetricsValues metricsValues = origin.get(i);
metricsValues.setValues(sortValues(metricsValues.getValues(), expectedOrder, defaultValue));
}
return origin;
}
/**
* Compose the multiple metric result based on conditions.
*/
public static List<MetricsValues> composeLabelValue(final String metricName,
final List<KeyValue> queryLabels,
final List<String> ids,
final Map<String, DataTable> idMap) {
final Optional<ValueColumnMetadata.ValueColumn> valueColumn
= ValueColumnMetadata.INSTANCE.readValueColumnDefinition(metricName);
if (valueColumn.isEmpty()) {
return Collections.emptyList();
}
//compatible with old version query
if (valueColumn.get().isMultiIntValues()) {
List<String> labelValues = buildLabelIndex(queryLabels, Const.COMMA).values()
.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
return composeLabelValueForMultiIntValues(metricName, labelValues, ids, idMap);
}
return composeLabelValueForMultipleLabels(metricName, queryLabels, ids, idMap);
}
public static List<String> composeLabelConditions(final List<KeyValue> queryLabels,
final Collection<DataTable> metricValues) {
LinkedHashMap<String, List<String>> queryLabelIndex = buildLabelIndex(queryLabels, Const.COMMA);
List<String> labelConditions = new ArrayList<>();
if (queryLabelIndex.isEmpty()) {
labelConditions = metricValues.stream()
.flatMap(dataTable -> dataTable.keys().stream())
.distinct()
.filter(k -> k.startsWith(Const.LEFT_BRACE))
.collect(Collectors.toList());
} else {
List<Set<String>> keySets = new ArrayList<>();
for (Map.Entry<String, List<String>> queryLabel : queryLabelIndex.entrySet()) {
Set<String> keySet = new HashSet<>();
metricValues.forEach(dataTable -> {
var metricLabelIndex = dataTable.buildLabelIndex();
for (String labelValue : queryLabel.getValue()) {
//union labels
keySet.addAll(metricLabelIndex.getOrDefault(
queryLabel.getKey() + Const.EQUAL + labelValue,
new HashSet<>()
));
}
});
if (!keySet.isEmpty()) {
keySets.add(keySet);
}
}
//intersection labels
keySets.stream().reduce((a, b) -> {
a.retainAll(b);
return a;
}).ifPresent(labelConditions::addAll);
}
return labelConditions;
}
private static List<MetricsValues> composeLabelValueForMultiIntValues(final String metricName,
final List<String> labels,
final List<String> ids,
final Map<String, DataTable> idMap) {
List<String> allLabels;
if (Objects.isNull(labels) || labels.isEmpty() || labels.stream().allMatch(Strings::isNullOrEmpty)) {
allLabels = idMap.values().stream()
.flatMap(dataTable -> dataTable.keys().stream())
.distinct().filter(k -> !k.startsWith(Const.LEFT_BRACE)).collect(Collectors.toList());
} else {
allLabels = labels;
}
return buildMetricsValues(metricName, ids, idMap, allLabels);
}
private static List<MetricsValues> composeLabelValueForMultipleLabels(final String metricName,
final List<KeyValue> queryLabels,
final List<String> ids,
final Map<String, DataTable> idMap) {
List<String> labelConditions = composeLabelConditions(queryLabels, idMap.values());
return buildMetricsValues(metricName, ids, idMap, labelConditions);
}
}
private static List<MetricsValues> buildMetricsValues(final String metricName,
final List<String> ids,
final Map<String, DataTable> idMap,
final List<String> allLabels) {
final int defaultValue = ValueColumnMetadata.INSTANCE.getDefaultValue(metricName);
final var labeledValues = new TreeSet<>(allLabels).stream()
.flatMap(label -> ids.stream().map(id -> {
final var value = idMap.getOrDefault(id, new DataTable());
return Objects.nonNull(value.get(label)) ?
new LabeledValue(
label,
id,
value.get(label), false) :
new LabeledValue(
label,
id,
defaultValue, true);
}))
.collect(toList());
MetricsValues current = new MetricsValues();
List<MetricsValues> result = new ArrayList<>();
for (LabeledValue each : labeledValues) {
if (Objects.equals(current.getLabel(), each.label)) {
current.getValues().addKVInt(each.kv);
} else {
current = new MetricsValues();
current.setLabel(each.label);
current.getValues().addKVInt(each.kv);
result.add(current);
}
}
return result;
}
private static LinkedHashMap<String, List<String>> buildLabelIndex(List<KeyValue> queryLabels, String separator) {
LinkedHashMap<String, List<String>> labelIndex = new LinkedHashMap<>();
if (null != queryLabels) {
for (KeyValue keyValue : queryLabels) {
String labelName = keyValue.getKey();
String labelValue = keyValue.getValue();
if (StringUtil.isNotBlank(labelValue)) {
String[] subValues = labelValue.split(separator);
for (String subValue : subValues) {
labelIndex.computeIfAbsent(labelName, key -> new ArrayList<>()).add(subValue);
}
}
}
}
return labelIndex;
}
class LabeledValue {
private final String label;
private final KVInt kv;
public LabeledValue(String label, String id, long value, boolean isEmptyValue) {
this.label = label;
this.kv = new KVInt(id, value, isEmptyValue);
}
}
}