mirror of
https://github.com/actiontech/dble.git
synced 2026-01-05 20:30:40 -06:00
fix: set the key of traceResult to connection id+node name+sql
inner 676 # Conflicts: # src/main/java/com/actiontech/dble/server/trace/TraceResult.java
This commit is contained in:
@@ -261,12 +261,13 @@ public class NonBlockingSession extends Session {
|
|||||||
long responseTime = 0;
|
long responseTime = 0;
|
||||||
if (traceEnable || SlowQueryLog.getInstance().isEnableSlowLog()) {
|
if (traceEnable || SlowQueryLog.getInstance().isEnableSlowLog()) {
|
||||||
RouteResultsetNode node = (RouteResultsetNode) service.getAttachment();
|
RouteResultsetNode node = (RouteResultsetNode) service.getAttachment();
|
||||||
if (traceResult.addToConnFlagMap(service.getConnection().getId() + ":" + node.getStatementHash()) == null) {
|
String key = service.getConnection().getId() + ":" + node.getName() + ":" + +node.getStatementHash();
|
||||||
|
if (traceResult.addToConnFlagMap(key) == null) {
|
||||||
ResponseHandler responseHandler = service.getResponseHandler();
|
ResponseHandler responseHandler = service.getResponseHandler();
|
||||||
responseTime = System.nanoTime();
|
responseTime = System.nanoTime();
|
||||||
TraceRecord record = new TraceRecord(responseTime, node.getName(), node.getStatement());
|
TraceRecord record = new TraceRecord(responseTime, node.getName(), node.getStatement());
|
||||||
Map<MySQLResponseService, TraceRecord> connMap = new ConcurrentHashMap<>();
|
Map<String, TraceRecord> connMap = new ConcurrentHashMap<>();
|
||||||
connMap.put(service, record);
|
connMap.put(key, record);
|
||||||
traceResult.addToConnReceivedMap(responseHandler, connMap);
|
traceResult.addToConnReceivedMap(responseHandler, connMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,8 +347,9 @@ public class NonBlockingSession extends Session {
|
|||||||
RouteResultsetNode node = (RouteResultsetNode) service.getAttachment();
|
RouteResultsetNode node = (RouteResultsetNode) service.getAttachment();
|
||||||
ResponseHandler responseHandler = service.getResponseHandler();
|
ResponseHandler responseHandler = service.getResponseHandler();
|
||||||
TraceRecord record = new TraceRecord(System.nanoTime(), node.getName(), node.getStatement());
|
TraceRecord record = new TraceRecord(System.nanoTime(), node.getName(), node.getStatement());
|
||||||
Map<MySQLResponseService, TraceRecord> connMap = new ConcurrentHashMap<>();
|
Map<String, TraceRecord> connMap = new ConcurrentHashMap<>();
|
||||||
connMap.put(service, record);
|
String key = service.getConnection().getId() + ":" + node.getName() + ":" + +node.getStatementHash();
|
||||||
|
connMap.put(key, record);
|
||||||
traceResult.addToConnFinishedMap(responseHandler, connMap);
|
traceResult.addToConnFinishedMap(responseHandler, connMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import com.actiontech.dble.backend.mysql.nio.handler.query.impl.OutputHandler;
|
|||||||
import com.actiontech.dble.plan.util.ComplexQueryPlanUtil;
|
import com.actiontech.dble.plan.util.ComplexQueryPlanUtil;
|
||||||
import com.actiontech.dble.plan.util.ReferenceHandlerInfo;
|
import com.actiontech.dble.plan.util.ReferenceHandlerInfo;
|
||||||
import com.actiontech.dble.route.RouteResultsetNode;
|
import com.actiontech.dble.route.RouteResultsetNode;
|
||||||
import com.actiontech.dble.services.mysqlsharding.MySQLResponseService;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -47,8 +46,8 @@ public class TraceResult implements Cloneable {
|
|||||||
private ResponseHandler simpleHandler = null;
|
private ResponseHandler simpleHandler = null;
|
||||||
private BaseHandlerBuilder builder = null; //for complex query
|
private BaseHandlerBuilder builder = null; //for complex query
|
||||||
private ConcurrentMap<String, Boolean> connFlagMap = new ConcurrentHashMap<>();
|
private ConcurrentMap<String, Boolean> connFlagMap = new ConcurrentHashMap<>();
|
||||||
private ConcurrentMap<ResponseHandler, Map<MySQLResponseService, TraceRecord>> connReceivedMap = new ConcurrentHashMap<>();
|
private ConcurrentMap<ResponseHandler, Map<String, TraceRecord>> connReceivedMap = new ConcurrentHashMap<>();
|
||||||
private ConcurrentMap<ResponseHandler, Map<MySQLResponseService, TraceRecord>> connFinishedMap = new ConcurrentHashMap<>();
|
private ConcurrentMap<ResponseHandler, Map<String, TraceRecord>> connFinishedMap = new ConcurrentHashMap<>();
|
||||||
private ConcurrentMap<DMLResponseHandler, TraceRecord> recordStartMap = new ConcurrentHashMap<>();
|
private ConcurrentMap<DMLResponseHandler, TraceRecord> recordStartMap = new ConcurrentHashMap<>();
|
||||||
private ConcurrentMap<DMLResponseHandler, TraceRecord> recordEndMap = new ConcurrentHashMap<>();
|
private ConcurrentMap<DMLResponseHandler, TraceRecord> recordEndMap = new ConcurrentHashMap<>();
|
||||||
private long veryEnd;
|
private long veryEnd;
|
||||||
@@ -127,25 +126,19 @@ public class TraceResult implements Cloneable {
|
|||||||
connFlagMap.clear();
|
connFlagMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addToConnReceivedMap(ResponseHandler responseHandler, Map<MySQLResponseService, TraceRecord> connMap) {
|
public void addToConnReceivedMap(ResponseHandler responseHandler, Map<String, TraceRecord> connMap) {
|
||||||
Map<MySQLResponseService, TraceRecord> existReceivedMap = connReceivedMap.putIfAbsent(responseHandler, connMap);
|
Map<String, TraceRecord> existReceivedMap = connReceivedMap.putIfAbsent(responseHandler, connMap);
|
||||||
if (existReceivedMap != null) {
|
if (existReceivedMap != null) {
|
||||||
existReceivedMap.putAll(connMap);
|
existReceivedMap.putAll(connMap);
|
||||||
}
|
}
|
||||||
if (LOGGER.isDebugEnabled()) {
|
|
||||||
LOGGER.debug("connReceivedMap:{}", connReceivedMap);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearConnReceivedMap() {
|
public void clearConnReceivedMap() {
|
||||||
if (LOGGER.isDebugEnabled()) {
|
|
||||||
LOGGER.debug("clear connReceivedMap:{}", connReceivedMap);
|
|
||||||
}
|
|
||||||
connReceivedMap.clear();
|
connReceivedMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addToConnFinishedMap(ResponseHandler responseHandler, Map<MySQLResponseService, TraceRecord> connMap) {
|
public void addToConnFinishedMap(ResponseHandler responseHandler, Map<String, TraceRecord> connMap) {
|
||||||
Map<MySQLResponseService, TraceRecord> existReceivedMap = connFinishedMap.putIfAbsent(responseHandler, connMap);
|
Map<String, TraceRecord> existReceivedMap = connFinishedMap.putIfAbsent(responseHandler, connMap);
|
||||||
if (existReceivedMap != null) {
|
if (existReceivedMap != null) {
|
||||||
existReceivedMap.putAll(connMap);
|
existReceivedMap.putAll(connMap);
|
||||||
}
|
}
|
||||||
@@ -189,14 +182,11 @@ public class TraceResult implements Cloneable {
|
|||||||
simpleHandler = null;
|
simpleHandler = null;
|
||||||
builder = null; //for complex query
|
builder = null; //for complex query
|
||||||
connFlagMap.clear();
|
connFlagMap.clear();
|
||||||
if (LOGGER.isDebugEnabled()) {
|
for (Map<String, TraceRecord> connReceived : connReceivedMap.values()) {
|
||||||
LOGGER.debug("clear connReceivedMap:{}", connReceivedMap);
|
|
||||||
}
|
|
||||||
for (Map<MySQLResponseService, TraceRecord> connReceived : connReceivedMap.values()) {
|
|
||||||
connReceived.clear();
|
connReceived.clear();
|
||||||
}
|
}
|
||||||
connReceivedMap.clear();
|
connReceivedMap.clear();
|
||||||
for (Map<MySQLResponseService, TraceRecord> connReceived : connFinishedMap.values()) {
|
for (Map<String, TraceRecord> connReceived : connFinishedMap.values()) {
|
||||||
connReceived.clear();
|
connReceived.clear();
|
||||||
}
|
}
|
||||||
connFinishedMap.clear();
|
connFinishedMap.clear();
|
||||||
@@ -305,7 +295,7 @@ public class TraceResult implements Cloneable {
|
|||||||
for (ReferenceHandlerInfo result : results) {
|
for (ReferenceHandlerInfo result : results) {
|
||||||
DMLResponseHandler handler = result.getHandler();
|
DMLResponseHandler handler = result.getHandler();
|
||||||
if (handler instanceof BaseSelectHandler) {
|
if (handler instanceof BaseSelectHandler) {
|
||||||
Map<MySQLResponseService, TraceRecord> fetchStartRecordMap = connReceivedMap.get(handler);
|
Map<String, TraceRecord> fetchStartRecordMap = connReceivedMap.get(handler);
|
||||||
if (fetchStartRecordMap == null) {
|
if (fetchStartRecordMap == null) {
|
||||||
if (!result.isNestLoopQuery()) {
|
if (!result.isNestLoopQuery()) {
|
||||||
lst.add(genTraceRecord("Execute_SQL", lastChildFinished, result.getName(), result.getRefOrSQL())); // lastChildFinished may is Long.MAX_VALUE
|
lst.add(genTraceRecord("Execute_SQL", lastChildFinished, result.getName(), result.getRefOrSQL())); // lastChildFinished may is Long.MAX_VALUE
|
||||||
@@ -330,7 +320,7 @@ public class TraceResult implements Cloneable {
|
|||||||
lst.add(genTraceRecord("Execute_SQL", handlerStart.getTimestamp(), handlerEnd.getTimestamp(), result.getName(), result.getRefOrSQL()));
|
lst.add(genTraceRecord("Execute_SQL", handlerStart.getTimestamp(), handlerEnd.getTimestamp(), result.getName(), result.getRefOrSQL()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Map<MySQLResponseService, TraceRecord> fetchEndRecordMap = connFinishedMap.get(handler);
|
Map<String, TraceRecord> fetchEndRecordMap = connFinishedMap.get(handler);
|
||||||
if (fetchEndRecordMap == null) {
|
if (fetchEndRecordMap == null) {
|
||||||
lst.add(genTraceRecord("Fetch_result", fetchStartRecord.getTimestamp(), result.getName(), result.getRefOrSQL()));
|
lst.add(genTraceRecord("Fetch_result", fetchStartRecord.getTimestamp(), result.getName(), result.getRefOrSQL()));
|
||||||
} else {
|
} else {
|
||||||
@@ -377,8 +367,8 @@ public class TraceResult implements Cloneable {
|
|||||||
for (ReferenceHandlerInfo result : results) {
|
for (ReferenceHandlerInfo result : results) {
|
||||||
DMLResponseHandler handler = result.getHandler();
|
DMLResponseHandler handler = result.getHandler();
|
||||||
if (handler instanceof BaseSelectHandler) {
|
if (handler instanceof BaseSelectHandler) {
|
||||||
Map<MySQLResponseService, TraceRecord> fetchStartRecordMap = connReceivedMap.get(handler);
|
Map<String, TraceRecord> fetchStartRecordMap = connReceivedMap.get(handler);
|
||||||
Map<MySQLResponseService, TraceRecord> fetchEndRecordMap = connFinishedMap.get(handler);
|
Map<String, TraceRecord> fetchEndRecordMap = connFinishedMap.get(handler);
|
||||||
if (fetchStartRecordMap == null || fetchEndRecordMap == null || fetchStartRecordMap.size() != 1 || fetchEndRecordMap.size() != 1) {
|
if (fetchStartRecordMap == null || fetchEndRecordMap == null || fetchStartRecordMap.size() != 1 || fetchEndRecordMap.size() != 1) {
|
||||||
printNoResultDebug(fetchStartRecordMap, fetchEndRecordMap);
|
printNoResultDebug(fetchStartRecordMap, fetchEndRecordMap);
|
||||||
return true;
|
return true;
|
||||||
@@ -427,7 +417,7 @@ public class TraceResult implements Cloneable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void printNoResultDebug(Map<MySQLResponseService, TraceRecord> fetchStartRecordMap, Map<MySQLResponseService, TraceRecord> fetchEndRecordMap) {
|
private void printNoResultDebug(Map<String, TraceRecord> fetchStartRecordMap, Map<String, TraceRecord> fetchEndRecordMap) {
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug("collect info not in pairs for connection");
|
LOGGER.debug("collect info not in pairs for connection");
|
||||||
if (fetchStartRecordMap != null) {
|
if (fetchStartRecordMap != null) {
|
||||||
@@ -442,13 +432,13 @@ public class TraceResult implements Cloneable {
|
|||||||
private boolean genSimpleResults(List<String[]> lst) {
|
private boolean genSimpleResults(List<String[]> lst) {
|
||||||
lst.add(genTraceRecord("Route_Calculation", routeStart.getTimestamp(), preExecuteStart.getTimestamp()));
|
lst.add(genTraceRecord("Route_Calculation", routeStart.getTimestamp(), preExecuteStart.getTimestamp()));
|
||||||
lst.add(genTraceRecord("Prepare_to_Push", preExecuteStart.getTimestamp(), preExecuteEnd.getTimestamp()));
|
lst.add(genTraceRecord("Prepare_to_Push", preExecuteStart.getTimestamp(), preExecuteEnd.getTimestamp()));
|
||||||
Map<MySQLResponseService, TraceRecord> connFetchStartMap = connReceivedMap.get(simpleHandler);
|
Map<String, TraceRecord> connFetchStartMap = connReceivedMap.get(simpleHandler);
|
||||||
Map<MySQLResponseService, TraceRecord> connFetchEndMap = connFinishedMap.get(simpleHandler);
|
Map<String, TraceRecord> connFetchEndMap = connFinishedMap.get(simpleHandler);
|
||||||
List<String[]> executeList = new ArrayList<>(connFetchStartMap.size());
|
List<String[]> executeList = new ArrayList<>(connFetchStartMap.size());
|
||||||
List<String[]> fetchList = new ArrayList<>(connFetchStartMap.size());
|
List<String[]> fetchList = new ArrayList<>(connFetchStartMap.size());
|
||||||
long minFetchStart = Long.MAX_VALUE;
|
long minFetchStart = Long.MAX_VALUE;
|
||||||
long maxFetchEnd = 0;
|
long maxFetchEnd = 0;
|
||||||
for (Map.Entry<MySQLResponseService, TraceRecord> fetchStart : connFetchStartMap.entrySet()) {
|
for (Map.Entry<String, TraceRecord> fetchStart : connFetchStartMap.entrySet()) {
|
||||||
TraceRecord fetchStartRecord = fetchStart.getValue();
|
TraceRecord fetchStartRecord = fetchStart.getValue();
|
||||||
minFetchStart = Math.min(minFetchStart, fetchStartRecord.getTimestamp());
|
minFetchStart = Math.min(minFetchStart, fetchStartRecord.getTimestamp());
|
||||||
executeList.add(genTraceRecord("Execute_SQL", preExecuteEnd.getTimestamp(), fetchStartRecord.getTimestamp(), fetchStartRecord.getShardingNode(), fetchStartRecord.getRef()));
|
executeList.add(genTraceRecord("Execute_SQL", preExecuteEnd.getTimestamp(), fetchStartRecord.getTimestamp(), fetchStartRecord.getShardingNode(), fetchStartRecord.getRef()));
|
||||||
@@ -471,16 +461,16 @@ public class TraceResult implements Cloneable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void genRunningSimpleResults(List<String[]> lst) {
|
private void genRunningSimpleResults(List<String[]> lst) {
|
||||||
Map<MySQLResponseService, TraceRecord> connFetchStartMap = connReceivedMap.get(simpleHandler);
|
Map<String, TraceRecord> connFetchStartMap = connReceivedMap.get(simpleHandler);
|
||||||
|
|
||||||
Set<String> receivedNode = new HashSet<>();
|
Set<String> receivedNode = new HashSet<>();
|
||||||
long minFetchStart = Long.MAX_VALUE;
|
long minFetchStart = Long.MAX_VALUE;
|
||||||
long maxFetchEnd = 0;
|
long maxFetchEnd = 0;
|
||||||
if (connFetchStartMap != null) {
|
if (connFetchStartMap != null) {
|
||||||
Map<MySQLResponseService, TraceRecord> connFetchEndMap = connFinishedMap.get(simpleHandler);
|
Map<String, TraceRecord> connFetchEndMap = connFinishedMap.get(simpleHandler);
|
||||||
List<String[]> executeList = new ArrayList<>(connFetchStartMap.size());
|
List<String[]> executeList = new ArrayList<>(connFetchStartMap.size());
|
||||||
List<String[]> fetchList = new ArrayList<>(connFetchStartMap.size());
|
List<String[]> fetchList = new ArrayList<>(connFetchStartMap.size());
|
||||||
for (Map.Entry<MySQLResponseService, TraceRecord> fetchStart : connFetchStartMap.entrySet()) {
|
for (Map.Entry<String, TraceRecord> fetchStart : connFetchStartMap.entrySet()) {
|
||||||
TraceRecord fetchStartRecord = fetchStart.getValue();
|
TraceRecord fetchStartRecord = fetchStart.getValue();
|
||||||
receivedNode.add(fetchStartRecord.getShardingNode());
|
receivedNode.add(fetchStartRecord.getShardingNode());
|
||||||
minFetchStart = Math.min(minFetchStart, fetchStartRecord.getTimestamp());
|
minFetchStart = Math.min(minFetchStart, fetchStartRecord.getTimestamp());
|
||||||
@@ -629,8 +619,8 @@ public class TraceResult implements Cloneable {
|
|||||||
for (ReferenceHandlerInfo result : results) {
|
for (ReferenceHandlerInfo result : results) {
|
||||||
DMLResponseHandler handler = result.getHandler();
|
DMLResponseHandler handler = result.getHandler();
|
||||||
if (handler instanceof BaseSelectHandler) {
|
if (handler instanceof BaseSelectHandler) {
|
||||||
Map<MySQLResponseService, TraceRecord> fetchStartRecordMap = connReceivedMap.get(handler);
|
Map<String, TraceRecord> fetchStartRecordMap = connReceivedMap.get(handler);
|
||||||
Map<MySQLResponseService, TraceRecord> fetchEndRecordMap = connFinishedMap.get(handler);
|
Map<String, TraceRecord> fetchEndRecordMap = connFinishedMap.get(handler);
|
||||||
if (fetchStartRecordMap == null || fetchEndRecordMap == null || fetchStartRecordMap.size() != 1 || fetchEndRecordMap.size() != 1) {
|
if (fetchStartRecordMap == null || fetchEndRecordMap == null || fetchStartRecordMap.size() != 1 || fetchEndRecordMap.size() != 1) {
|
||||||
printNoResultDebug(fetchStartRecordMap, fetchEndRecordMap);
|
printNoResultDebug(fetchStartRecordMap, fetchEndRecordMap);
|
||||||
return true;
|
return true;
|
||||||
@@ -679,13 +669,13 @@ public class TraceResult implements Cloneable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean genSimpleLogs(List<String[]> lst) {
|
private boolean genSimpleLogs(List<String[]> lst) {
|
||||||
Map<MySQLResponseService, TraceRecord> connFetchStartMap = connReceivedMap.get(simpleHandler);
|
Map<String, TraceRecord> connFetchStartMap = connReceivedMap.get(simpleHandler);
|
||||||
Map<MySQLResponseService, TraceRecord> connFetchEndMap = connFinishedMap.get(simpleHandler);
|
Map<String, TraceRecord> connFetchEndMap = connFinishedMap.get(simpleHandler);
|
||||||
List<String[]> executeList = new ArrayList<>(connFetchStartMap.size());
|
List<String[]> executeList = new ArrayList<>(connFetchStartMap.size());
|
||||||
List<String[]> fetchList = new ArrayList<>(connFetchStartMap.size());
|
List<String[]> fetchList = new ArrayList<>(connFetchStartMap.size());
|
||||||
long minFetchStart = Long.MAX_VALUE;
|
long minFetchStart = Long.MAX_VALUE;
|
||||||
long maxFetchEnd = 0;
|
long maxFetchEnd = 0;
|
||||||
for (Map.Entry<MySQLResponseService, TraceRecord> fetchStart : connFetchStartMap.entrySet()) {
|
for (Map.Entry<String, TraceRecord> fetchStart : connFetchStartMap.entrySet()) {
|
||||||
TraceRecord fetchStartRecord = fetchStart.getValue();
|
TraceRecord fetchStartRecord = fetchStart.getValue();
|
||||||
minFetchStart = Math.min(minFetchStart, fetchStartRecord.getTimestamp());
|
minFetchStart = Math.min(minFetchStart, fetchStartRecord.getTimestamp());
|
||||||
executeList.add(genLogRecord(fetchStartRecord.getShardingNode() + "_First_Result_Fetch", preExecuteEnd.getTimestamp(), fetchStartRecord.getTimestamp()));
|
executeList.add(genLogRecord(fetchStartRecord.getShardingNode() + "_First_Result_Fetch", preExecuteEnd.getTimestamp(), fetchStartRecord.getTimestamp()));
|
||||||
@@ -733,14 +723,14 @@ public class TraceResult implements Cloneable {
|
|||||||
tr.connFlagMap = new ConcurrentHashMap<>();
|
tr.connFlagMap = new ConcurrentHashMap<>();
|
||||||
tr.connFlagMap.putAll(this.connFlagMap);
|
tr.connFlagMap.putAll(this.connFlagMap);
|
||||||
tr.connReceivedMap = new ConcurrentHashMap<>();
|
tr.connReceivedMap = new ConcurrentHashMap<>();
|
||||||
for (Map.Entry<ResponseHandler, Map<MySQLResponseService, TraceRecord>> item : connReceivedMap.entrySet()) {
|
for (Map.Entry<ResponseHandler, Map<String, TraceRecord>> item : connReceivedMap.entrySet()) {
|
||||||
Map<MySQLResponseService, TraceRecord> connMap = new ConcurrentHashMap<>();
|
Map<String, TraceRecord> connMap = new ConcurrentHashMap<>();
|
||||||
connMap.putAll(item.getValue());
|
connMap.putAll(item.getValue());
|
||||||
tr.connReceivedMap.put(item.getKey(), connMap);
|
tr.connReceivedMap.put(item.getKey(), connMap);
|
||||||
}
|
}
|
||||||
tr.connFinishedMap = new ConcurrentHashMap<>();
|
tr.connFinishedMap = new ConcurrentHashMap<>();
|
||||||
for (Map.Entry<ResponseHandler, Map<MySQLResponseService, TraceRecord>> item : connFinishedMap.entrySet()) {
|
for (Map.Entry<ResponseHandler, Map<String, TraceRecord>> item : connFinishedMap.entrySet()) {
|
||||||
Map<MySQLResponseService, TraceRecord> connMap = new ConcurrentHashMap<>();
|
Map<String, TraceRecord> connMap = new ConcurrentHashMap<>();
|
||||||
connMap.putAll(item.getValue());
|
connMap.putAll(item.getValue());
|
||||||
tr.connFinishedMap.put(item.getKey(), connMap);
|
tr.connFinishedMap.put(item.getKey(), connMap);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user