提交 d897eccc 编写于 作者: P peng-yongsheng

Provide error and application query condition in trace stack UI.

#401
上级 928e782d
...@@ -38,6 +38,7 @@ public class SegmentCostSpanListener implements EntrySpanListener, ExitSpanListe ...@@ -38,6 +38,7 @@ public class SegmentCostSpanListener implements EntrySpanListener, ExitSpanListe
public void parseEntry(SpanObject spanObject, int applicationId, int applicationInstanceId, String segmentId) { public void parseEntry(SpanObject spanObject, int applicationId, int applicationInstanceId, String segmentId) {
SegmentCostDataDefine.SegmentCost segmentCost = new SegmentCostDataDefine.SegmentCost(); SegmentCostDataDefine.SegmentCost segmentCost = new SegmentCostDataDefine.SegmentCost();
segmentCost.setSegmentId(segmentId); segmentCost.setSegmentId(segmentId);
segmentCost.setApplicationId(applicationId);
segmentCost.setCost(spanObject.getEndTime() - spanObject.getStartTime()); segmentCost.setCost(spanObject.getEndTime() - spanObject.getStartTime());
segmentCost.setStartTime(spanObject.getStartTime()); segmentCost.setStartTime(spanObject.getStartTime());
segmentCost.setEndTime(spanObject.getEndTime()); segmentCost.setEndTime(spanObject.getEndTime());
......
...@@ -4,11 +4,11 @@ import java.util.HashMap; ...@@ -4,11 +4,11 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.update.UpdateRequestBuilder; import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.skywalking.apm.collector.core.stream.Data;
import org.skywalking.apm.collector.storage.define.DataDefine;
import org.skywalking.apm.collector.storage.define.segment.SegmentCostTable; import org.skywalking.apm.collector.storage.define.segment.SegmentCostTable;
import org.skywalking.apm.collector.storage.elasticsearch.dao.EsDAO; import org.skywalking.apm.collector.storage.elasticsearch.dao.EsDAO;
import org.skywalking.apm.collector.stream.worker.impl.dao.IPersistenceDAO; import org.skywalking.apm.collector.stream.worker.impl.dao.IPersistenceDAO;
import org.skywalking.apm.collector.core.stream.Data;
import org.skywalking.apm.collector.storage.define.DataDefine;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -31,6 +31,7 @@ public class SegmentCostEsDAO extends EsDAO implements ISegmentCostDAO, IPersist ...@@ -31,6 +31,7 @@ public class SegmentCostEsDAO extends EsDAO implements ISegmentCostDAO, IPersist
logger.debug("segment cost prepareBatchInsert, id: {}", data.getDataString(0)); logger.debug("segment cost prepareBatchInsert, id: {}", data.getDataString(0));
Map<String, Object> source = new HashMap<>(); Map<String, Object> source = new HashMap<>();
source.put(SegmentCostTable.COLUMN_SEGMENT_ID, data.getDataString(1)); source.put(SegmentCostTable.COLUMN_SEGMENT_ID, data.getDataString(1));
source.put(SegmentCostTable.COLUMN_APPLICATION_ID, data.getDataInteger(0));
source.put(SegmentCostTable.COLUMN_SERVICE_NAME, data.getDataString(2)); source.put(SegmentCostTable.COLUMN_SERVICE_NAME, data.getDataString(2));
source.put(SegmentCostTable.COLUMN_COST, data.getDataLong(0)); source.put(SegmentCostTable.COLUMN_COST, data.getDataLong(0));
source.put(SegmentCostTable.COLUMN_START_TIME, data.getDataLong(1)); source.put(SegmentCostTable.COLUMN_START_TIME, data.getDataLong(1));
......
...@@ -19,6 +19,7 @@ public class SegmentCostEsTableDefine extends ElasticSearchTableDefine { ...@@ -19,6 +19,7 @@ public class SegmentCostEsTableDefine extends ElasticSearchTableDefine {
@Override public void initialize() { @Override public void initialize() {
addColumn(new ElasticSearchColumnDefine(SegmentCostTable.COLUMN_SEGMENT_ID, ElasticSearchColumnDefine.Type.Keyword.name())); addColumn(new ElasticSearchColumnDefine(SegmentCostTable.COLUMN_SEGMENT_ID, ElasticSearchColumnDefine.Type.Keyword.name()));
addColumn(new ElasticSearchColumnDefine(SegmentCostTable.COLUMN_APPLICATION_ID, ElasticSearchColumnDefine.Type.Integer.name()));
addColumn(new ElasticSearchColumnDefine(SegmentCostTable.COLUMN_SERVICE_NAME, ElasticSearchColumnDefine.Type.Text.name())); addColumn(new ElasticSearchColumnDefine(SegmentCostTable.COLUMN_SERVICE_NAME, ElasticSearchColumnDefine.Type.Text.name()));
addColumn(new ElasticSearchColumnDefine(SegmentCostTable.COLUMN_COST, ElasticSearchColumnDefine.Type.Long.name())); addColumn(new ElasticSearchColumnDefine(SegmentCostTable.COLUMN_COST, ElasticSearchColumnDefine.Type.Long.name()));
addColumn(new ElasticSearchColumnDefine(SegmentCostTable.COLUMN_START_TIME, ElasticSearchColumnDefine.Type.Long.name())); addColumn(new ElasticSearchColumnDefine(SegmentCostTable.COLUMN_START_TIME, ElasticSearchColumnDefine.Type.Long.name()));
......
package org.skywalking.apm.collector.agentstream.worker.segment.cost.define; package org.skywalking.apm.collector.agentstream.worker.segment.cost.define;
import org.skywalking.apm.collector.storage.define.segment.SegmentCostTable;
import org.skywalking.apm.collector.storage.h2.define.H2ColumnDefine; import org.skywalking.apm.collector.storage.h2.define.H2ColumnDefine;
import org.skywalking.apm.collector.storage.h2.define.H2TableDefine; import org.skywalking.apm.collector.storage.h2.define.H2TableDefine;
import org.skywalking.apm.collector.storage.define.segment.SegmentCostTable;
/** /**
* @author pengys5 * @author pengys5
...@@ -16,6 +16,7 @@ public class SegmentCostH2TableDefine extends H2TableDefine { ...@@ -16,6 +16,7 @@ public class SegmentCostH2TableDefine extends H2TableDefine {
@Override public void initialize() { @Override public void initialize() {
addColumn(new H2ColumnDefine(SegmentCostTable.COLUMN_ID, H2ColumnDefine.Type.Varchar.name())); addColumn(new H2ColumnDefine(SegmentCostTable.COLUMN_ID, H2ColumnDefine.Type.Varchar.name()));
addColumn(new H2ColumnDefine(SegmentCostTable.COLUMN_SEGMENT_ID, H2ColumnDefine.Type.Varchar.name())); addColumn(new H2ColumnDefine(SegmentCostTable.COLUMN_SEGMENT_ID, H2ColumnDefine.Type.Varchar.name()));
addColumn(new H2ColumnDefine(SegmentCostTable.COLUMN_APPLICATION_ID, H2ColumnDefine.Type.Int.name()));
addColumn(new H2ColumnDefine(SegmentCostTable.COLUMN_SERVICE_NAME, H2ColumnDefine.Type.Varchar.name())); addColumn(new H2ColumnDefine(SegmentCostTable.COLUMN_SERVICE_NAME, H2ColumnDefine.Type.Varchar.name()));
addColumn(new H2ColumnDefine(SegmentCostTable.COLUMN_COST, H2ColumnDefine.Type.Bigint.name())); addColumn(new H2ColumnDefine(SegmentCostTable.COLUMN_COST, H2ColumnDefine.Type.Bigint.name()));
addColumn(new H2ColumnDefine(SegmentCostTable.COLUMN_START_TIME, H2ColumnDefine.Type.Bigint.name())); addColumn(new H2ColumnDefine(SegmentCostTable.COLUMN_START_TIME, H2ColumnDefine.Type.Bigint.name()));
......
...@@ -15,18 +15,19 @@ import org.skywalking.apm.collector.storage.define.DataDefine; ...@@ -15,18 +15,19 @@ import org.skywalking.apm.collector.storage.define.DataDefine;
public class SegmentCostDataDefine extends DataDefine { public class SegmentCostDataDefine extends DataDefine {
@Override protected int initialCapacity() { @Override protected int initialCapacity() {
return 8; return 9;
} }
@Override protected void attributeDefine() { @Override protected void attributeDefine() {
addAttribute(0, new Attribute(SegmentCostTable.COLUMN_ID, AttributeType.STRING, new NonOperation())); addAttribute(0, new Attribute(SegmentCostTable.COLUMN_ID, AttributeType.STRING, new NonOperation()));
addAttribute(1, new Attribute(SegmentCostTable.COLUMN_SEGMENT_ID, AttributeType.STRING, new CoverOperation())); addAttribute(1, new Attribute(SegmentCostTable.COLUMN_SEGMENT_ID, AttributeType.STRING, new CoverOperation()));
addAttribute(2, new Attribute(SegmentCostTable.COLUMN_SERVICE_NAME, AttributeType.STRING, new CoverOperation())); addAttribute(2, new Attribute(SegmentCostTable.COLUMN_APPLICATION_ID, AttributeType.INTEGER, new CoverOperation()));
addAttribute(3, new Attribute(SegmentCostTable.COLUMN_COST, AttributeType.LONG, new CoverOperation())); addAttribute(3, new Attribute(SegmentCostTable.COLUMN_SERVICE_NAME, AttributeType.STRING, new CoverOperation()));
addAttribute(4, new Attribute(SegmentCostTable.COLUMN_START_TIME, AttributeType.LONG, new CoverOperation())); addAttribute(4, new Attribute(SegmentCostTable.COLUMN_COST, AttributeType.LONG, new CoverOperation()));
addAttribute(5, new Attribute(SegmentCostTable.COLUMN_END_TIME, AttributeType.LONG, new CoverOperation())); addAttribute(5, new Attribute(SegmentCostTable.COLUMN_START_TIME, AttributeType.LONG, new CoverOperation()));
addAttribute(6, new Attribute(SegmentCostTable.COLUMN_IS_ERROR, AttributeType.BOOLEAN, new CoverOperation())); addAttribute(6, new Attribute(SegmentCostTable.COLUMN_END_TIME, AttributeType.LONG, new CoverOperation()));
addAttribute(7, new Attribute(SegmentCostTable.COLUMN_TIME_BUCKET, AttributeType.LONG, new NonOperation())); addAttribute(7, new Attribute(SegmentCostTable.COLUMN_IS_ERROR, AttributeType.BOOLEAN, new CoverOperation()));
addAttribute(8, new Attribute(SegmentCostTable.COLUMN_TIME_BUCKET, AttributeType.LONG, new NonOperation()));
} }
@Override public Object deserialize(RemoteData remoteData) { @Override public Object deserialize(RemoteData remoteData) {
...@@ -39,6 +40,7 @@ public class SegmentCostDataDefine extends DataDefine { ...@@ -39,6 +40,7 @@ public class SegmentCostDataDefine extends DataDefine {
public static class SegmentCost implements Transform { public static class SegmentCost implements Transform {
private String id; private String id;
private int applicationId;
private String segmentId; private String segmentId;
private String serviceName; private String serviceName;
private Long cost; private Long cost;
...@@ -56,6 +58,7 @@ public class SegmentCostDataDefine extends DataDefine { ...@@ -56,6 +58,7 @@ public class SegmentCostDataDefine extends DataDefine {
data.setDataString(0, this.id); data.setDataString(0, this.id);
data.setDataString(1, this.segmentId); data.setDataString(1, this.segmentId);
data.setDataString(2, this.serviceName); data.setDataString(2, this.serviceName);
data.setDataInteger(0, this.applicationId);
data.setDataLong(0, this.cost); data.setDataLong(0, this.cost);
data.setDataLong(1, this.startTime); data.setDataLong(1, this.startTime);
data.setDataLong(2, this.endTime); data.setDataLong(2, this.endTime);
...@@ -131,5 +134,13 @@ public class SegmentCostDataDefine extends DataDefine { ...@@ -131,5 +134,13 @@ public class SegmentCostDataDefine extends DataDefine {
public void setTimeBucket(long timeBucket) { public void setTimeBucket(long timeBucket) {
this.timeBucket = timeBucket; this.timeBucket = timeBucket;
} }
public int getApplicationId() {
return applicationId;
}
public void setApplicationId(int applicationId) {
this.applicationId = applicationId;
}
} }
} }
...@@ -8,6 +8,7 @@ import org.skywalking.apm.collector.storage.define.CommonTable; ...@@ -8,6 +8,7 @@ import org.skywalking.apm.collector.storage.define.CommonTable;
public class SegmentCostTable extends CommonTable { public class SegmentCostTable extends CommonTable {
public static final String TABLE = "segment_cost"; public static final String TABLE = "segment_cost";
public static final String COLUMN_SEGMENT_ID = "segment_id"; public static final String COLUMN_SEGMENT_ID = "segment_id";
public static final String COLUMN_APPLICATION_ID = "application_id";
public static final String COLUMN_START_TIME = "start_time"; public static final String COLUMN_START_TIME = "start_time";
public static final String COLUMN_END_TIME = "end_time"; public static final String COLUMN_END_TIME = "end_time";
public static final String COLUMN_SERVICE_NAME = "service_name"; public static final String COLUMN_SERVICE_NAME = "service_name";
......
...@@ -8,9 +8,13 @@ import java.util.List; ...@@ -8,9 +8,13 @@ import java.util.List;
*/ */
public interface ISegmentCostDAO { public interface ISegmentCostDAO {
JsonObject loadTop(long startTime, long endTime, long minCost, long maxCost, String operationName, JsonObject loadTop(long startTime, long endTime, long minCost, long maxCost, String operationName,
List<String> segmentIds, int limit, int from, Sort sort); Error error, int applicationId, List<String> segmentIds, int limit, int from, Sort sort);
public enum Sort { enum Sort {
Cost, Time Cost, Time
} }
enum Error {
All, True, False
}
} }
...@@ -25,7 +25,7 @@ import org.skywalking.apm.collector.storage.elasticsearch.dao.EsDAO; ...@@ -25,7 +25,7 @@ import org.skywalking.apm.collector.storage.elasticsearch.dao.EsDAO;
public class SegmentCostEsDAO extends EsDAO implements ISegmentCostDAO { public class SegmentCostEsDAO extends EsDAO implements ISegmentCostDAO {
@Override public JsonObject loadTop(long startTime, long endTime, long minCost, long maxCost, String operationName, @Override public JsonObject loadTop(long startTime, long endTime, long minCost, long maxCost, String operationName,
List<String> segmentIds, int limit, int from, Sort sort) { Error error, int applicationId, List<String> segmentIds, int limit, int from, Sort sort) {
SearchRequestBuilder searchRequestBuilder = getClient().prepareSearch(SegmentCostTable.TABLE); SearchRequestBuilder searchRequestBuilder = getClient().prepareSearch(SegmentCostTable.TABLE);
searchRequestBuilder.setTypes(SegmentCostTable.TABLE_TYPE); searchRequestBuilder.setTypes(SegmentCostTable.TABLE_TYPE);
searchRequestBuilder.setSearchType(SearchType.DFS_QUERY_THEN_FETCH); searchRequestBuilder.setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
...@@ -50,6 +50,14 @@ public class SegmentCostEsDAO extends EsDAO implements ISegmentCostDAO { ...@@ -50,6 +50,14 @@ public class SegmentCostEsDAO extends EsDAO implements ISegmentCostDAO {
if (CollectionUtils.isNotEmpty(segmentIds)) { if (CollectionUtils.isNotEmpty(segmentIds)) {
boolQueryBuilder.must().add(QueryBuilders.termsQuery(SegmentCostTable.COLUMN_SEGMENT_ID, segmentIds.toArray(new String[0]))); boolQueryBuilder.must().add(QueryBuilders.termsQuery(SegmentCostTable.COLUMN_SEGMENT_ID, segmentIds.toArray(new String[0])));
} }
if (Error.True.equals(error)) {
boolQueryBuilder.must().add(QueryBuilders.termQuery(SegmentCostTable.COLUMN_IS_ERROR, true));
} else if (Error.False.equals(error)) {
boolQueryBuilder.must().add(QueryBuilders.termQuery(SegmentCostTable.COLUMN_IS_ERROR, false));
}
if (applicationId != 0) {
boolQueryBuilder.must().add(QueryBuilders.termQuery(SegmentCostTable.COLUMN_APPLICATION_ID, applicationId));
}
if (Sort.Cost.equals(sort)) { if (Sort.Cost.equals(sort)) {
searchRequestBuilder.addSort(SegmentCostTable.COLUMN_COST, SortOrder.DESC); searchRequestBuilder.addSort(SegmentCostTable.COLUMN_COST, SortOrder.DESC);
...@@ -84,6 +92,7 @@ public class SegmentCostEsDAO extends EsDAO implements ISegmentCostDAO { ...@@ -84,6 +92,7 @@ public class SegmentCostEsDAO extends EsDAO implements ISegmentCostDAO {
topSegmentJson.addProperty(GlobalTraceTable.COLUMN_GLOBAL_TRACE_ID, globalTraces.get(0)); topSegmentJson.addProperty(GlobalTraceTable.COLUMN_GLOBAL_TRACE_ID, globalTraces.get(0));
} }
topSegmentJson.addProperty(SegmentCostTable.COLUMN_APPLICATION_ID, (Number)searchHit.getSource().get(SegmentCostTable.COLUMN_APPLICATION_ID));
topSegmentJson.addProperty(SegmentCostTable.COLUMN_SERVICE_NAME, (String)searchHit.getSource().get(SegmentCostTable.COLUMN_SERVICE_NAME)); topSegmentJson.addProperty(SegmentCostTable.COLUMN_SERVICE_NAME, (String)searchHit.getSource().get(SegmentCostTable.COLUMN_SERVICE_NAME));
topSegmentJson.addProperty(SegmentCostTable.COLUMN_COST, (Number)searchHit.getSource().get(SegmentCostTable.COLUMN_COST)); topSegmentJson.addProperty(SegmentCostTable.COLUMN_COST, (Number)searchHit.getSource().get(SegmentCostTable.COLUMN_COST));
topSegmentJson.addProperty(SegmentCostTable.COLUMN_IS_ERROR, (Boolean)searchHit.getSource().get(SegmentCostTable.COLUMN_IS_ERROR)); topSegmentJson.addProperty(SegmentCostTable.COLUMN_IS_ERROR, (Boolean)searchHit.getSource().get(SegmentCostTable.COLUMN_IS_ERROR));
......
...@@ -9,7 +9,7 @@ import org.skywalking.apm.collector.storage.h2.dao.H2DAO; ...@@ -9,7 +9,7 @@ import org.skywalking.apm.collector.storage.h2.dao.H2DAO;
*/ */
public class SegmentCostH2DAO extends H2DAO implements ISegmentCostDAO { public class SegmentCostH2DAO extends H2DAO implements ISegmentCostDAO {
@Override public JsonObject loadTop(long startTime, long endTime, long minCost, long maxCost, String operationName, @Override public JsonObject loadTop(long startTime, long endTime, long minCost, long maxCost, String operationName,
List<String> segmentIds, int limit, int from, Sort sort) { Error error, int applicationId, List<String> segmentIds, int limit, int from, Sort sort) {
return null; return null;
} }
} }
...@@ -2,6 +2,7 @@ package org.skywalking.apm.collector.ui.jetty.handler; ...@@ -2,6 +2,7 @@ package org.skywalking.apm.collector.ui.jetty.handler;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.skywalking.apm.collector.core.util.StringUtils;
import org.skywalking.apm.collector.server.jetty.ArgumentsParseException; import org.skywalking.apm.collector.server.jetty.ArgumentsParseException;
import org.skywalking.apm.collector.server.jetty.JettyHandler; import org.skywalking.apm.collector.server.jetty.JettyHandler;
import org.skywalking.apm.collector.ui.dao.ISegmentCostDAO; import org.skywalking.apm.collector.ui.dao.ISegmentCostDAO;
...@@ -78,6 +79,27 @@ public class SegmentTopGetHandler extends JettyHandler { ...@@ -78,6 +79,27 @@ public class SegmentTopGetHandler extends JettyHandler {
operationName = req.getParameter("operationName"); operationName = req.getParameter("operationName");
} }
int applicationId;
try {
applicationId = Integer.valueOf(req.getParameter("applicationId"));
} catch (NumberFormatException e) {
throw new ArgumentsParseException("the request parameter applicationId must be a int");
}
ISegmentCostDAO.Error error;
String errorStr = req.getParameter("error");
if (StringUtils.isNotEmpty(errorStr)) {
if ("true".equals(errorStr)) {
error = ISegmentCostDAO.Error.True;
} else if ("false".equals(errorStr)) {
error = ISegmentCostDAO.Error.False;
} else {
error = ISegmentCostDAO.Error.All;
}
} else {
error = ISegmentCostDAO.Error.All;
}
ISegmentCostDAO.Sort sort = ISegmentCostDAO.Sort.Cost; ISegmentCostDAO.Sort sort = ISegmentCostDAO.Sort.Cost;
if (req.getParameterMap().containsKey("sort")) { if (req.getParameterMap().containsKey("sort")) {
String sortStr = req.getParameter("sort"); String sortStr = req.getParameter("sort");
...@@ -86,7 +108,7 @@ public class SegmentTopGetHandler extends JettyHandler { ...@@ -86,7 +108,7 @@ public class SegmentTopGetHandler extends JettyHandler {
} }
} }
return service.loadTop(startTime, endTime, minCost, maxCost, operationName, globalTraceId, limit, from, sort); return service.loadTop(startTime, endTime, minCost, maxCost, operationName, globalTraceId, error, applicationId, limit, from, sort);
} }
@Override protected JsonElement doPost(HttpServletRequest req) throws ArgumentsParseException { @Override protected JsonElement doPost(HttpServletRequest req) throws ArgumentsParseException {
......
...@@ -18,8 +18,9 @@ public class SegmentTopService { ...@@ -18,8 +18,9 @@ public class SegmentTopService {
private final Logger logger = LoggerFactory.getLogger(SegmentTopService.class); private final Logger logger = LoggerFactory.getLogger(SegmentTopService.class);
public JsonObject loadTop(long startTime, long endTime, long minCost, long maxCost, String operationName, public JsonObject loadTop(long startTime, long endTime, long minCost, long maxCost, String operationName,
String globalTraceId, int limit, int from, ISegmentCostDAO.Sort sort) { String globalTraceId, ISegmentCostDAO.Error error, int applicationId, int limit, int from,
logger.debug("startTime: {}, endTime: {}, minCost: {}, maxCost: {}, operationName: {}, globalTraceId: {}, limit: {}, from: {}", startTime, endTime, minCost, maxCost, operationName, globalTraceId, limit, from); ISegmentCostDAO.Sort sort) {
logger.debug("startTime: {}, endTime: {}, minCost: {}, maxCost: {}, operationName: {}, globalTraceId: {}, error: {}, applicationId: {}, limit: {}, from: {}", startTime, endTime, minCost, maxCost, operationName, globalTraceId, error, applicationId, limit, from);
List<String> segmentIds = new LinkedList<>(); List<String> segmentIds = new LinkedList<>();
if (StringUtils.isNotEmpty(globalTraceId)) { if (StringUtils.isNotEmpty(globalTraceId)) {
...@@ -27,6 +28,6 @@ public class SegmentTopService { ...@@ -27,6 +28,6 @@ public class SegmentTopService {
segmentIds = globalTraceDAO.getSegmentIds(globalTraceId); segmentIds = globalTraceDAO.getSegmentIds(globalTraceId);
} }
ISegmentCostDAO segmentCostDAO = (ISegmentCostDAO)DAOContainer.INSTANCE.get(ISegmentCostDAO.class.getName()); ISegmentCostDAO segmentCostDAO = (ISegmentCostDAO)DAOContainer.INSTANCE.get(ISegmentCostDAO.class.getName());
return segmentCostDAO.loadTop(startTime, endTime, minCost, maxCost, operationName, segmentIds, limit, from, sort); return segmentCostDAO.loadTop(startTime, endTime, minCost, maxCost, operationName, error, applicationId, segmentIds, limit, from, sort);
} }
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册