| /* |
| * 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.ignite.development.utils; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| import org.apache.ignite.internal.pagemem.wal.record.DataEntry; |
| import org.apache.ignite.internal.processors.cache.version.GridCacheVersion; |
| |
| /** |
| * Transactions statistics for WAL. |
| */ |
| public class TxWalStat { |
| /** Maximum element in popular combinations discovery map before eviction. */ |
| private static final int POPULAR_COMBINATION_MAP_MAX_SIZE = 50000; |
| |
| /** Usages count, when evict prohibited from popular combinations discovery map. */ |
| private static final int USAGES_CNT_EVICT_PROHIBITED = 10; |
| |
| /** Transactions in preparing/committing state. When commit is finished, TX is removed from this collection. */ |
| private final Map<GridCacheVersion, TxOccurrence> opened = new HashMap<>(); |
| |
| /** Field for calculating average number of primary nodes involved in Tx. */ |
| private final RecordSizeCountStat avgPrimaryNodes = new RecordSizeCountStat(); |
| |
| /** Field for calculating average number of total nodes involved in Tx. */ |
| private final RecordSizeCountStat avgTotalNodes = new RecordSizeCountStat(); |
| |
| /** Tx statistics: Entries updated count -> count of such Txes. */ |
| private final Map<Integer, Integer> txEntriesUpdated = new TreeMap<>(); |
| |
| /** Tx statistics: Caches Involved count -> count of such Txes. */ |
| private final Map<Integer, Integer> txCachesInvolved = new TreeMap<>(); |
| |
| /** Cache IDs combination involved in Tx. Popular combination search map, with possible eviction. */ |
| private final LruMap<String> cacheIdsInTx = new LruMap<>(POPULAR_COMBINATION_MAP_MAX_SIZE, |
| USAGES_CNT_EVICT_PROHIBITED); |
| |
| /** |
| * Cache IDs combination involved in Tx, weighted using primary nodes in Tx. Used to search popular combinations |
| * mostly involved into highly distributive transactions. |
| */ |
| private final LruMap<String> cacheIdsWeightedNodesInTx = new LruMap<>(POPULAR_COMBINATION_MAP_MAX_SIZE, |
| USAGES_CNT_EVICT_PROHIBITED * 90); |
| /** |
| * Cache IDs combination involved in Tx, weighted using total nodes in Tx. Used to search popular combinations |
| * mostly involved into highly distributive transactions. |
| */ |
| private final LruMap<String> cacheIdsWeightedTotalNodesInTx = new LruMap<>(POPULAR_COMBINATION_MAP_MAX_SIZE, |
| USAGES_CNT_EVICT_PROHIBITED * 150); |
| |
| /** |
| * @param key key (parameter) value found. |
| * @param map map to save increment. |
| * @param <K> type of key. |
| */ |
| private static <K> void incrementStat(K key, Map<K, Integer> map) { |
| incrementStat(key, map, 1); |
| } |
| |
| /** |
| * @param key key (parameter) value found. |
| * @param map map to save increment. |
| * @param increment value to increment statistic, 1 or weight of current occurrence. |
| * @param <K> type of key. |
| */ |
| private static <K> void incrementStat(K key, Map<K, Integer> map, int increment) { |
| Integer val = map.get(key); |
| int recordStat = val == null ? 0 : val; |
| |
| recordStat += increment; |
| map.put(key, recordStat); |
| } |
| |
| /** |
| * Handles TX prepare: creates TX in {@link #opened} map. |
| * @param nearXidVer near Xid Version. Global transaction identifier within cluster, assigned by transaction |
| * coordinator. |
| * @param nodes primary nodes registered in prepare record. |
| * @param totalNodes all nodes (primary & backup) in prepare record. |
| */ |
| void onTxPrepareStart(GridCacheVersion nearXidVer, int nodes, int totalNodes) { |
| txComputeIfAbsent(nearXidVer, nodes, totalNodes); |
| } |
| |
| /** |
| * @param nearXidVer near Xid Version. Global transaction identifier within cluster, assigned by transaction |
| * coordinator. |
| * @param nodes primary nodes registered in prepare record. |
| * @param totalNodes all nodes (primary & backup) in prepare record. |
| * @return tx occurrence to accumulate entries into. |
| */ |
| private TxOccurrence txComputeIfAbsent(GridCacheVersion nearXidVer, int nodes, int totalNodes) { |
| TxOccurrence occurrence = opened.get(nearXidVer); |
| |
| if (occurrence == null) |
| occurrence = new TxOccurrence(nodes, totalNodes); |
| |
| opened.put(nearXidVer, occurrence); |
| |
| return occurrence; |
| } |
| |
| /** |
| * Handles commit or rollback transaction. Finished statistics accumulation. |
| * |
| * @param nearXidVer near Xid Version. Global transaction identifier within cluster, assigned by transaction |
| * coordinator. |
| * @param commit tx committed, flag indicating TX successes. |
| */ |
| void onTxEnd(GridCacheVersion nearXidVer, boolean commit) { |
| TxOccurrence occurrence = opened.remove(nearXidVer); |
| |
| if (occurrence == null) |
| return; |
| |
| if (!commit) |
| return; |
| |
| if (occurrence.nodes > 0 && occurrence.totalNodes > 0) { |
| avgPrimaryNodes.occurrence(occurrence.nodes); |
| avgTotalNodes.occurrence(occurrence.totalNodes); |
| } |
| |
| incrementStat(occurrence.entriesUpdated, txEntriesUpdated); |
| |
| incrementStat(occurrence.caches.size(), txCachesInvolved); |
| |
| if (!occurrence.caches.isEmpty()) { |
| final String sortedCachesKey = occurrence.caches.toString(); |
| |
| incrementStat(sortedCachesKey, cacheIdsInTx.map, 1); |
| |
| if (occurrence.nodes > 0) |
| incrementStat(sortedCachesKey, cacheIdsWeightedNodesInTx.map, occurrence.nodes); |
| |
| if (occurrence.totalNodes > 0) |
| incrementStat(sortedCachesKey, cacheIdsWeightedTotalNodesInTx.map, occurrence.totalNodes); |
| } |
| } |
| |
| /** |
| * Handles Data entry from data record. Entries not under transaction are ignored. |
| * |
| * @param entry object updated. |
| */ |
| void onDataEntry(DataEntry entry) { |
| final GridCacheVersion ver = entry.nearXidVersion(); |
| |
| if (ver == null) |
| return; |
| |
| txComputeIfAbsent(ver, -1, -1).onDataEntry(entry); |
| } |
| |
| /** |
| * @param sb buffer. |
| * @param mapName display name of map. |
| * @param map values. |
| */ |
| private void printSizeCountMap(StringBuilder sb, String mapName, Map<?, Integer> map) { |
| sb.append(mapName).append(": \n"); |
| sb.append("key\tcount"); |
| sb.append("\n"); |
| |
| final List<? extends Map.Entry<?, Integer>> entries = new ArrayList<>(map.entrySet()); |
| |
| Collections.sort(entries, new Comparator<Map.Entry<?, Integer>>() { |
| @Override public int compare(Map.Entry<?, Integer> o1, Map.Entry<?, Integer> o2) { |
| return -Integer.compare(o1.getValue(), o2.getValue()); |
| } |
| }); |
| |
| int othersCnt = 0; |
| int othersSum = 0; |
| int cnt = 0; |
| |
| for (Map.Entry<?, Integer> next : entries) { |
| if (cnt < WalStat.DISPLAY_MAX) { |
| sb.append(next.getKey()).append("\t").append(next.getValue()).append("\t"); |
| sb.append("\n"); |
| } |
| else { |
| othersCnt++; |
| othersSum += next.getValue(); |
| } |
| cnt++; |
| } |
| |
| if (othersCnt > 0) { |
| sb.append("... other ").append(othersCnt).append(" values").append("\t").append(othersSum).append("\t"); |
| sb.append("\n"); |
| } |
| |
| sb.append("\n"); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String toString() { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("Tx stat: remained Opened: \t").append(opened.size()).append("\n").append("\n"); |
| |
| sb.append("Tx stat: Average Primary Node Count: \t").append(avgPrimaryNodes.averageStr()).append("\n").append("\n"); |
| |
| sb.append("Tx stat: Average Total Node Count: \t").append(avgTotalNodes.averageStr()).append("\n").append("\n"); |
| |
| printSizeCountMap(sb, "Tx stat: Entries updated", txEntriesUpdated); |
| printSizeCountMap(sb, "Tx stat: Caches involved", txCachesInvolved); |
| printSizeCountMap(sb, "Tx stat: Caches list in TX, evicted = " + cacheIdsInTx.evicted, cacheIdsInTx.map); |
| |
| printSizeCountMap(sb, "Tx stat: Caches list in TX; weighted by primary Nodes, evicted = " |
| + cacheIdsWeightedNodesInTx.evicted, cacheIdsWeightedNodesInTx.map); |
| |
| printSizeCountMap(sb, "Tx stat: Caches list in TX; weighted by total Nodes, evicted = " |
| + cacheIdsWeightedNodesInTx.evicted, cacheIdsWeightedNodesInTx.map); |
| return sb.toString(); |
| } |
| |
| /** |
| * Tx in prepare or in commit state, used to accumulate statistic. |
| */ |
| private static class TxOccurrence { |
| /** Primary nodes count from TX record. */ |
| private int nodes; |
| /** Primary + backup nodes count from TX record. */ |
| private int totalNodes; |
| |
| /** Count of entries updated under current transaction on current node. */ |
| private int entriesUpdated; |
| |
| /** Sorted set of cache IDs updated during this transaction. */ |
| private TreeSet<Integer> caches = new TreeSet<>(); |
| |
| /** |
| * @param nodes Primary nodes count from TX record. |
| * @param totalNodes Primary + backup nodes count from TX record. |
| */ |
| TxOccurrence(int nodes, int totalNodes) { |
| this.nodes = nodes; |
| this.totalNodes = totalNodes; |
| } |
| |
| /** |
| * Handles data entry from data record. |
| * @param entry object updated. |
| */ |
| void onDataEntry(DataEntry entry) { |
| entriesUpdated++; |
| |
| caches.add(entry.cacheId()); |
| } |
| } |
| |
| /** |
| * @param <K> key type parameter. |
| */ |
| private static class LruMap<K> { |
| /** Max size of map after which eviction may start. */ |
| private int maxSize; |
| |
| /** |
| * Evict prohibited boundary. If this number of usages is accumulate in eldest entry it will not be removed |
| * anyway. |
| */ |
| private int evictProhibited; |
| /** |
| * Evicted count. Number of entries removed during statistic accumulation. Zero value means all records were |
| * processed, created top (popular combination search) is totally correct. Non zero means top may be not |
| * correct. |
| */ |
| private int evicted; |
| |
| /** Map with data. */ |
| private Map<K, Integer> map = new LinkedHashMap<K, Integer>(16, 0.75F, false) { |
| @Override protected boolean removeEldestEntry(Map.Entry<K, Integer> eldest) { |
| if (size() < maxSize) |
| return false; |
| |
| final boolean evictNow = eldest.getValue() < evictProhibited; |
| |
| if (evictNow) |
| evicted++; |
| |
| return evictNow; |
| } |
| }; |
| |
| /** |
| * @param maxSize Max size of map. |
| * @param evictProhibited usages count, when evict became prohibited. |
| */ |
| LruMap(int maxSize, int evictProhibited) { |
| this.maxSize = maxSize; |
| this.evictProhibited = evictProhibited; |
| } |
| } |
| } |