mirror of
https://github.com/wahyd4/pinpoint.git
synced 2026-08-23 03:36:31 +10:00
[유치수] [NOBTS] add applicationName
git-svn-id: http://svn.bds.nhncorp.com/pe/hippo-web/trunk@846 84d0f5b1-2673-498c-a247-62c4ff18d310
This commit is contained in:
@@ -7,16 +7,18 @@ public class Server implements Comparable<Server> {
|
||||
private int sequence;
|
||||
private final String id;
|
||||
private final String agentId;
|
||||
private final String applicationName;
|
||||
private final String endPoint;
|
||||
private final boolean terminal;
|
||||
|
||||
public Server(String agentId, String endPoint, boolean terminal) {
|
||||
// this.id = agentId + ":" + endPoint;
|
||||
this.id = endPoint;
|
||||
this.agentId = agentId;
|
||||
this.endPoint = endPoint;
|
||||
this.terminal = terminal;
|
||||
}
|
||||
public Server(String agentId, String applicationName, String endPoint, boolean terminal) {
|
||||
// this.id = agentId + ":" + endPoint;
|
||||
this.id = endPoint;
|
||||
this.agentId = agentId;
|
||||
this.applicationName = applicationName;
|
||||
this.endPoint = endPoint;
|
||||
this.terminal = terminal;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
@@ -42,7 +44,11 @@ public class Server implements Comparable<Server> {
|
||||
return terminal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApplicationName() {
|
||||
return applicationName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Server server) {
|
||||
return id.compareTo(server.id);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class ServerCallTree {
|
||||
* make Servers
|
||||
*/
|
||||
// TODO: 여기에서 이러지말고 수집할 때 처음부터 table에 저장해둘 수 있나??
|
||||
Server server = new Server(span.getAgentId(), span.getEndPoint(), span.isTerminal());
|
||||
Server server = new Server(span.getAgentId(), span.getApplicationName(), span.getEndPoint(), span.isTerminal());
|
||||
|
||||
if (server.getId() == null) {
|
||||
return;
|
||||
|
||||
@@ -32,8 +32,7 @@ public class FlowChartController {
|
||||
|
||||
@RequestMapping(value = "/flow", method = RequestMethod.GET)
|
||||
public String flow(Model model, @RequestParam("application") String applicationName, @RequestParam("from") long from, @RequestParam("to") long to) {
|
||||
String[] agentIds = flow.selectAgentIdsFromApplicationName(applicationName);
|
||||
Set<TraceId> traceIds = flow.selectTraceIdsFromTraceIndex(agentIds, from, to);
|
||||
Set<TraceId> traceIds = flow.selectTraceIdsFromApplicationTraceIndex(applicationName, from, to);
|
||||
|
||||
RPCCallTree callTree = flow.selectRPCCallTree(traceIds);
|
||||
|
||||
@@ -47,15 +46,62 @@ public class FlowChartController {
|
||||
|
||||
@RequestMapping(value = "/flowserver", method = RequestMethod.GET)
|
||||
public String flowserver(Model model, @RequestParam("application") String applicationName, @RequestParam("from") long from, @RequestParam("to") long to) {
|
||||
String[] agentIds = flow.selectAgentIdsFromApplicationName(applicationName);
|
||||
// TODO 제거 하거나, interceptor로 할것.
|
||||
StopWatch watch = new StopWatch();
|
||||
watch.start("scanTraceindex");
|
||||
Set<TraceId> traceIds = flow.selectTraceIdsFromTraceIndex(agentIds, from, to);
|
||||
|
||||
Set<TraceId> traceIds = flow.selectTraceIdsFromApplicationTraceIndex(applicationName, from, to);
|
||||
|
||||
watch.stop();
|
||||
logger.info("time:{} {}", watch.getLastTaskTimeMillis(), traceIds.size());
|
||||
watch.start("selectServerCallTree");
|
||||
|
||||
ServerCallTree callTree = flow.selectServerCallTree(traceIds);
|
||||
|
||||
watch.stop();
|
||||
logger.info("time:{}", watch.getLastTaskTimeMillis());
|
||||
|
||||
model.addAttribute("nodes", callTree.getNodes());
|
||||
model.addAttribute("links", callTree.getLinks());
|
||||
model.addAttribute("businessTransactions", callTree.getBusinessTransactions().getBusinessTransactionIterator());
|
||||
model.addAttribute("traces", callTree.getBusinessTransactions().getTracesIterator());
|
||||
|
||||
logger.debug("callTree:{}", callTree);
|
||||
|
||||
return "flowserver";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/flow2", method = RequestMethod.GET)
|
||||
public String flowbyHost(Model model, @RequestParam("host") String[] hosts, @RequestParam("from") long from, @RequestParam("to") long to) {
|
||||
String[] agentIds = flow.selectAgentIds(hosts);
|
||||
Set<TraceId> traceIds = flow.selectTraceIdsFromTraceIndex(agentIds, from, to);
|
||||
|
||||
RPCCallTree callTree = flow.selectRPCCallTree(traceIds);
|
||||
|
||||
model.addAttribute("nodes", callTree.getNodes());
|
||||
model.addAttribute("links", callTree.getLinks());
|
||||
|
||||
logger.debug("callTree:{}", callTree);
|
||||
|
||||
return "flow";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/flowserver2", method = RequestMethod.GET)
|
||||
public String flowserverByHost(Model model, @RequestParam("host") String[] hosts, @RequestParam("from") long from, @RequestParam("to") long to) {
|
||||
String[] agentIds = flow.selectAgentIds(hosts);
|
||||
|
||||
// TODO 제거 하거나, interceptor로 할것.
|
||||
StopWatch watch = new StopWatch();
|
||||
watch.start("scanTraceindex");
|
||||
|
||||
Set<TraceId> traceIds = flow.selectTraceIdsFromTraceIndex(agentIds, from, to);
|
||||
|
||||
watch.stop();
|
||||
logger.info("time:{} {}", watch.getLastTaskTimeMillis(), traceIds.size());
|
||||
watch.start("selectServerCallTree");
|
||||
|
||||
ServerCallTree callTree = flow.selectServerCallTree(traceIds);
|
||||
|
||||
watch.stop();
|
||||
logger.info("time:{}", watch.getLastTaskTimeMillis());
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.nhn.hippo.web.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface ApplicationTraceIndexDao {
|
||||
List<byte[]> scanTraceIndex(String applicationName, long start, long end);
|
||||
|
||||
List<List<byte[]>> multiScanTraceIndex(String[] applicationNames, long start, long end);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.nhn.hippo.web.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.hadoop.hbase.client.Get;
|
||||
import org.apache.hadoop.hbase.client.Scan;
|
||||
import org.apache.hadoop.hbase.util.Bytes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.hadoop.hbase.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.profiler.common.hbase.HBaseTables;
|
||||
import com.profiler.common.hbase.HbaseOperations2;
|
||||
|
||||
/**
|
||||
* @author netspider
|
||||
*/
|
||||
@Repository
|
||||
public class HbaseApplicationIndexDao implements ApplicationIndexDao {
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
|
||||
|
||||
@Autowired
|
||||
private HbaseOperations2 hbaseOperations2;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("applicationNameMapper")
|
||||
private RowMapper<String> applicationNameMapper;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("agentIdMapper")
|
||||
private RowMapper<String[]> agentIdMapper;
|
||||
|
||||
@Override
|
||||
public List<String> selectAllApplicationNames() {
|
||||
Scan scan = new Scan();
|
||||
scan.setCaching(30);
|
||||
return hbaseOperations2.find(HBaseTables.APPLICATION_INDEX, scan, applicationNameMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] selectAgentIds(String applicationName) {
|
||||
byte[] rowKey = Bytes.toBytes(applicationName);
|
||||
|
||||
Get get = new Get(rowKey);
|
||||
get.addFamily(HBaseTables.APPLICATION_CF_AGENTS);
|
||||
|
||||
return hbaseOperations2.get(HBaseTables.APPLICATION_INDEX, get, agentIdMapper);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.nhn.hippo.web.dao;
|
||||
|
||||
import com.profiler.common.hbase.HBaseTables;
|
||||
import com.profiler.common.hbase.HbaseOperations2;
|
||||
import com.profiler.common.util.SpanUtils;
|
||||
import org.apache.hadoop.hbase.client.Scan;
|
||||
import org.apache.hadoop.hbase.util.Bytes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.hadoop.hbase.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Repository
|
||||
public class HbaseRootTraceIndexDao implements RootTraceIndexDao {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private final byte[] COLFAM_TRACE = Bytes.toBytes("Trace");
|
||||
private final byte[] COLNAME_ID = Bytes.toBytes("ID");
|
||||
|
||||
@Autowired
|
||||
private HbaseOperations2 hbaseOperations2;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("traceIndexMapper")
|
||||
private RowMapper<byte[]> traceIndexMapper;
|
||||
|
||||
|
||||
private int scanCacheSize = 40;
|
||||
|
||||
public void setScanCacheSize(int scanCacheSize) {
|
||||
this.scanCacheSize = scanCacheSize;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<byte[]> scanTraceIndex(String agent, long start, long end) {
|
||||
Scan scan = createScan(agent, start, end);
|
||||
return hbaseOperations2.find(HBaseTables.ROOT_TRACE_INDEX, scan, traceIndexMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<byte[]>> multiScanTraceIndex(String[] agents, long start, long end) {
|
||||
final List<Scan> multiScan = new ArrayList<Scan>(agents.length);
|
||||
for (String agent : agents) {
|
||||
Scan scan = createScan(agent, start, end);
|
||||
multiScan.add(scan);
|
||||
}
|
||||
return hbaseOperations2.find(HBaseTables.ROOT_TRACE_INDEX, multiScan, traceIndexMapper);
|
||||
}
|
||||
|
||||
private Scan createScan(String agent, long start, long end) {
|
||||
Scan scan = new Scan();
|
||||
// cache size를 지정해야 되는거 같음.??
|
||||
scan.setCaching(this.scanCacheSize);
|
||||
|
||||
byte[] bAgent = Bytes.toBytes(agent);
|
||||
byte[] traceIndexStartKey = SpanUtils.getTraceIndexRowKey(bAgent, start);
|
||||
scan.setStartRow(traceIndexStartKey);
|
||||
|
||||
byte[] traceIndexEndKey = SpanUtils.getTraceIndexRowKey(bAgent, end);
|
||||
scan.setStopRow(traceIndexEndKey);
|
||||
|
||||
scan.addColumn(COLFAM_TRACE, COLNAME_ID);
|
||||
scan.setId("rootTraceIndexScan");
|
||||
|
||||
// json으로 변화해서 로그를 찍어서. 최초 변환 속도가 느림.
|
||||
logger.debug("create scan:{}", scan);
|
||||
return scan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List parallelScanTraceIndex(String[] agents, long start, long end) {
|
||||
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package com.nhn.hippo.web.dao;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.hadoop.hbase.client.Get;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.hadoop.hbase.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.nhn.hippo.web.vo.TraceId;
|
||||
import com.profiler.common.bo.SpanBo;
|
||||
import com.profiler.common.hbase.HBaseTables;
|
||||
import com.profiler.common.hbase.HbaseOperations2;
|
||||
import com.profiler.common.util.BytesUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Repository
|
||||
public class HbaseTraceDao implements TraceDao {
|
||||
|
||||
private final byte[] COLFAM_SPAN = HBaseTables.TRACES_CF_SPAN;
|
||||
|
||||
private final byte[] COLFAM_ANNOTATION = HBaseTables.TRACES_CF_ANNOTATION;
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private HbaseOperations2 template2;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("spanMapper")
|
||||
private RowMapper<List<SpanBo>> spanMapper;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("spanAnnotationMapper")
|
||||
private RowMapper<List<SpanBo>> spanAnnotationMapper;
|
||||
|
||||
@Override
|
||||
public List<SpanBo> selectSpan(UUID traceId) {
|
||||
byte[] uuidBytes = BytesUtils.longLongToBytes(traceId.getMostSignificantBits(), traceId.getLeastSignificantBits());
|
||||
return template2.get(HBaseTables.TRACES, uuidBytes, COLFAM_SPAN, spanMapper);
|
||||
}
|
||||
|
||||
public List<SpanBo> selectSpanAndAnnotation(UUID traceId) {
|
||||
byte[] uuidBytes = BytesUtils.longLongToBytes(traceId.getMostSignificantBits(), traceId.getLeastSignificantBits());
|
||||
Get get = new Get(uuidBytes);
|
||||
get.addFamily(COLFAM_SPAN);
|
||||
get.addFamily(COLFAM_ANNOTATION);
|
||||
return template2.get(HBaseTables.TRACES, get, spanAnnotationMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SpanBo> selectSpan(long traceIdMost, long traceIdLeast) {
|
||||
byte[] uuidBytes = BytesUtils.longLongToBytes(traceIdMost, traceIdLeast);
|
||||
return template2.get(HBaseTables.TRACES, uuidBytes, COLFAM_SPAN, spanMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<SpanBo>> selectSpans(List<UUID> traceIds) {
|
||||
List<Get> gets = new ArrayList<Get>(traceIds.size());
|
||||
for (UUID traceId : traceIds) {
|
||||
byte[] uuidBytes = BytesUtils.longLongToBytes(traceId.getMostSignificantBits(), traceId.getLeastSignificantBits());
|
||||
Get get = new Get(uuidBytes);
|
||||
get.addFamily(COLFAM_SPAN);
|
||||
gets.add(get);
|
||||
}
|
||||
return template2.get(HBaseTables.TRACES, gets, spanMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<SpanBo>> selectSpans(Set<TraceId> traceIds) {
|
||||
List<Get> gets = new ArrayList<Get>(traceIds.size());
|
||||
for (TraceId traceId : traceIds) {
|
||||
Get get = new Get(traceId.getBytes());
|
||||
get.addFamily(COLFAM_SPAN);
|
||||
gets.add(get);
|
||||
}
|
||||
return template2.get(HBaseTables.TRACES, gets, spanMapper);
|
||||
}
|
||||
|
||||
public List<List<SpanBo>> selectSpansAndAnnotation(Set<TraceId> traceIds) {
|
||||
List<Get> gets = new ArrayList<Get>(traceIds.size());
|
||||
for (TraceId traceId : traceIds) {
|
||||
Get get = new Get(traceId.getBytes());
|
||||
get.addFamily(COLFAM_SPAN);
|
||||
get.addFamily(COLFAM_ANNOTATION);
|
||||
gets.add(get);
|
||||
}
|
||||
return template2.get(HBaseTables.TRACES, gets, spanAnnotationMapper);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package com.nhn.hippo.web.dao;
|
||||
|
||||
import com.profiler.common.hbase.HBaseTables;
|
||||
import com.profiler.common.hbase.HbaseOperations2;
|
||||
import com.profiler.common.util.SpanUtils;
|
||||
import org.apache.hadoop.hbase.client.Scan;
|
||||
import org.apache.hadoop.hbase.util.Bytes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.hadoop.hbase.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Repository
|
||||
public class HbaseTraceIndexDao implements TraceIndexDao {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private final byte[] COLFAM_TRACE = Bytes.toBytes("Trace");
|
||||
private final byte[] COLNAME_ID = Bytes.toBytes("ID");
|
||||
|
||||
@Autowired
|
||||
private HbaseOperations2 hbaseOperations2;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("traceIndexMapper")
|
||||
private RowMapper<byte[]> traceIndexMapper;
|
||||
|
||||
|
||||
private int scanCacheSize = 40;
|
||||
|
||||
public void setScanCacheSize(int scanCacheSize) {
|
||||
this.scanCacheSize = scanCacheSize;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<byte[]> scanTraceIndex(String agent, long start, long end) {
|
||||
Scan scan = createScan(agent, start, end);
|
||||
return hbaseOperations2.find(HBaseTables.TRACE_INDEX, scan, traceIndexMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<byte[]>> multiScanTraceIndex(String[] agents, long start, long end) {
|
||||
final List<Scan> multiScan = new ArrayList<Scan>(agents.length);
|
||||
for (String agent : agents) {
|
||||
Scan scan = createScan(agent, start, end);
|
||||
multiScan.add(scan);
|
||||
}
|
||||
return hbaseOperations2.find(HBaseTables.TRACE_INDEX, multiScan, traceIndexMapper);
|
||||
}
|
||||
|
||||
|
||||
private Scan createScan(String agent, long start, long end) {
|
||||
|
||||
Scan scan = new Scan();
|
||||
// cache size를 지정해야 되는거 같음.??
|
||||
scan.setCaching(this.scanCacheSize);
|
||||
|
||||
byte[] bAgent = Bytes.toBytes(agent);
|
||||
byte[] traceIndexStartKey = SpanUtils.getTraceIndexRowKey(bAgent, start);
|
||||
scan.setStartRow(traceIndexStartKey);
|
||||
// TODO 추가 filter를 구현하여 scan시 중복된 값을 제가 할수 있음. 단 server에도 Filter 클래스가 배포되어야 한다.
|
||||
// scan.setFilter(new ValueFilter());
|
||||
|
||||
byte[] traceIndexEndKey = SpanUtils.getTraceIndexRowKey(bAgent, end);
|
||||
scan.setStopRow(traceIndexEndKey);
|
||||
scan.addColumn(COLFAM_TRACE, COLNAME_ID);
|
||||
scan.setId("traceIndexScan");
|
||||
|
||||
// json으로 변화해서 로그를 찍어서. 최초 변환 속도가 느림.
|
||||
logger.debug("create scan:{}", scan);
|
||||
return scan;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.nhn.hippo.web.dao.hbase;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.hadoop.hbase.client.Get;
|
||||
import org.apache.hadoop.hbase.client.Scan;
|
||||
import org.apache.hadoop.hbase.util.Bytes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.hadoop.hbase.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.nhn.hippo.web.dao.ApplicationIndexDao;
|
||||
import com.profiler.common.hbase.HBaseTables;
|
||||
import com.profiler.common.hbase.HbaseOperations2;
|
||||
|
||||
/**
|
||||
* @author netspider
|
||||
*/
|
||||
@Repository
|
||||
public class HbaseApplicationIndexDao implements ApplicationIndexDao {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
|
||||
|
||||
@Autowired
|
||||
private HbaseOperations2 hbaseOperations2;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("applicationNameMapper")
|
||||
private RowMapper<String> applicationNameMapper;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("agentIdMapper")
|
||||
private RowMapper<String[]> agentIdMapper;
|
||||
|
||||
@Override
|
||||
public List<String> selectAllApplicationNames() {
|
||||
Scan scan = new Scan();
|
||||
scan.setCaching(30);
|
||||
return hbaseOperations2.find(HBaseTables.APPLICATION_INDEX, scan, applicationNameMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] selectAgentIds(String applicationName) {
|
||||
byte[] rowKey = Bytes.toBytes(applicationName);
|
||||
|
||||
Get get = new Get(rowKey);
|
||||
get.addFamily(HBaseTables.APPLICATION_INDEX_CF_AGENTS);
|
||||
|
||||
return hbaseOperations2.get(HBaseTables.APPLICATION_INDEX, get, agentIdMapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.nhn.hippo.web.dao.hbase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.hadoop.hbase.client.Scan;
|
||||
import org.apache.hadoop.hbase.util.Bytes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.hadoop.hbase.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.nhn.hippo.web.dao.ApplicationTraceIndexDao;
|
||||
import com.profiler.common.hbase.HBaseTables;
|
||||
import com.profiler.common.hbase.HbaseOperations2;
|
||||
import com.profiler.common.util.SpanUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Repository
|
||||
public class HbaseApplicationTraceIndexDao implements ApplicationTraceIndexDao {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private final byte[] COLFAM_TRACE = HBaseTables.APPLICATION_TRACE_INDEX_CF_TRACE;
|
||||
private final byte[] COLNAME_ID = HBaseTables.APPLICATION_TRACE_INDEX_CN_ID;
|
||||
|
||||
@Autowired
|
||||
private HbaseOperations2 hbaseOperations2;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("traceIndexMapper")
|
||||
private RowMapper<byte[]> traceIndexMapper;
|
||||
|
||||
private int scanCacheSize = 40;
|
||||
|
||||
public void setScanCacheSize(int scanCacheSize) {
|
||||
this.scanCacheSize = scanCacheSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<byte[]> scanTraceIndex(String applicationName, long start, long end) {
|
||||
Scan scan = createScan(applicationName, start, end);
|
||||
return hbaseOperations2.find(HBaseTables.APPLICATION_TRACE_INDEX, scan, traceIndexMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<byte[]>> multiScanTraceIndex(String[] applicationNames, long start, long end) {
|
||||
final List<Scan> multiScan = new ArrayList<Scan>(applicationNames.length);
|
||||
for (String agent : applicationNames) {
|
||||
Scan scan = createScan(agent, start, end);
|
||||
multiScan.add(scan);
|
||||
}
|
||||
return hbaseOperations2.find(HBaseTables.APPLICATION_TRACE_INDEX, multiScan, traceIndexMapper);
|
||||
}
|
||||
|
||||
private Scan createScan(String agent, long start, long end) {
|
||||
Scan scan = new Scan();
|
||||
// cache size를 지정해야 되는거 같음.??
|
||||
scan.setCaching(this.scanCacheSize);
|
||||
|
||||
byte[] bAgent = Bytes.toBytes(agent);
|
||||
byte[] traceIndexStartKey = SpanUtils.getTraceIndexRowKey(bAgent, start);
|
||||
scan.setStartRow(traceIndexStartKey);
|
||||
// TODO 추가 filter를 구현하여 scan시 중복된 값을 제가 할수 있음. 단 server에도 Filter 클래스가
|
||||
// 배포되어야 한다.
|
||||
// scan.setFilter(new ValueFilter());
|
||||
|
||||
byte[] traceIndexEndKey = SpanUtils.getTraceIndexRowKey(bAgent, end);
|
||||
scan.setStopRow(traceIndexEndKey);
|
||||
scan.addColumn(COLFAM_TRACE, COLNAME_ID);
|
||||
scan.setId("traceIndexScan");
|
||||
|
||||
// json으로 변화해서 로그를 찍어서. 최초 변환 속도가 느림.
|
||||
logger.debug("create scan:{}", scan);
|
||||
return scan;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.nhn.hippo.web.dao.hbase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.hadoop.hbase.client.Scan;
|
||||
import org.apache.hadoop.hbase.util.Bytes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.hadoop.hbase.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.nhn.hippo.web.dao.RootTraceIndexDao;
|
||||
import com.profiler.common.hbase.HBaseTables;
|
||||
import com.profiler.common.hbase.HbaseOperations2;
|
||||
import com.profiler.common.util.SpanUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Repository
|
||||
public class HbaseRootTraceIndexDao implements RootTraceIndexDao {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private final byte[] COLFAM_TRACE = Bytes.toBytes("Trace");
|
||||
private final byte[] COLNAME_ID = Bytes.toBytes("ID");
|
||||
|
||||
@Autowired
|
||||
private HbaseOperations2 hbaseOperations2;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("traceIndexMapper")
|
||||
private RowMapper<byte[]> traceIndexMapper;
|
||||
|
||||
private int scanCacheSize = 40;
|
||||
|
||||
public void setScanCacheSize(int scanCacheSize) {
|
||||
this.scanCacheSize = scanCacheSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<byte[]> scanTraceIndex(String agent, long start, long end) {
|
||||
Scan scan = createScan(agent, start, end);
|
||||
return hbaseOperations2.find(HBaseTables.ROOT_TRACE_INDEX, scan, traceIndexMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<byte[]>> multiScanTraceIndex(String[] agents, long start, long end) {
|
||||
final List<Scan> multiScan = new ArrayList<Scan>(agents.length);
|
||||
for (String agent : agents) {
|
||||
Scan scan = createScan(agent, start, end);
|
||||
multiScan.add(scan);
|
||||
}
|
||||
return hbaseOperations2.find(HBaseTables.ROOT_TRACE_INDEX, multiScan, traceIndexMapper);
|
||||
}
|
||||
|
||||
private Scan createScan(String agent, long start, long end) {
|
||||
Scan scan = new Scan();
|
||||
// cache size를 지정해야 되는거 같음.??
|
||||
scan.setCaching(this.scanCacheSize);
|
||||
|
||||
byte[] bAgent = Bytes.toBytes(agent);
|
||||
byte[] traceIndexStartKey = SpanUtils.getTraceIndexRowKey(bAgent, start);
|
||||
scan.setStartRow(traceIndexStartKey);
|
||||
|
||||
byte[] traceIndexEndKey = SpanUtils.getTraceIndexRowKey(bAgent, end);
|
||||
scan.setStopRow(traceIndexEndKey);
|
||||
|
||||
scan.addColumn(COLFAM_TRACE, COLNAME_ID);
|
||||
scan.setId("rootTraceIndexScan");
|
||||
|
||||
// json으로 변화해서 로그를 찍어서. 최초 변환 속도가 느림.
|
||||
logger.debug("create scan:{}", scan);
|
||||
return scan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List parallelScanTraceIndex(String[] agents, long start, long end) {
|
||||
return null; // To change body of implemented methods use File |
|
||||
// Settings | File Templates.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.nhn.hippo.web.dao.hbase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.hadoop.hbase.client.Get;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.hadoop.hbase.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.nhn.hippo.web.dao.TraceDao;
|
||||
import com.nhn.hippo.web.vo.TraceId;
|
||||
import com.profiler.common.bo.SpanBo;
|
||||
import com.profiler.common.hbase.HBaseTables;
|
||||
import com.profiler.common.hbase.HbaseOperations2;
|
||||
import com.profiler.common.util.BytesUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Repository
|
||||
public class HbaseTraceDao implements TraceDao {
|
||||
|
||||
private final byte[] COLFAM_SPAN = HBaseTables.TRACES_CF_SPAN;
|
||||
|
||||
private final byte[] COLFAM_ANNOTATION = HBaseTables.TRACES_CF_ANNOTATION;
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private HbaseOperations2 template2;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("spanMapper")
|
||||
private RowMapper<List<SpanBo>> spanMapper;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("spanAnnotationMapper")
|
||||
private RowMapper<List<SpanBo>> spanAnnotationMapper;
|
||||
|
||||
@Override
|
||||
public List<SpanBo> selectSpan(UUID traceId) {
|
||||
byte[] uuidBytes = BytesUtils.longLongToBytes(traceId.getMostSignificantBits(), traceId.getLeastSignificantBits());
|
||||
return template2.get(HBaseTables.TRACES, uuidBytes, COLFAM_SPAN, spanMapper);
|
||||
}
|
||||
|
||||
public List<SpanBo> selectSpanAndAnnotation(UUID traceId) {
|
||||
byte[] uuidBytes = BytesUtils.longLongToBytes(traceId.getMostSignificantBits(), traceId.getLeastSignificantBits());
|
||||
Get get = new Get(uuidBytes);
|
||||
get.addFamily(COLFAM_SPAN);
|
||||
get.addFamily(COLFAM_ANNOTATION);
|
||||
return template2.get(HBaseTables.TRACES, get, spanAnnotationMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SpanBo> selectSpan(long traceIdMost, long traceIdLeast) {
|
||||
byte[] uuidBytes = BytesUtils.longLongToBytes(traceIdMost, traceIdLeast);
|
||||
return template2.get(HBaseTables.TRACES, uuidBytes, COLFAM_SPAN, spanMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<SpanBo>> selectSpans(List<UUID> traceIds) {
|
||||
List<Get> gets = new ArrayList<Get>(traceIds.size());
|
||||
for (UUID traceId : traceIds) {
|
||||
byte[] uuidBytes = BytesUtils.longLongToBytes(traceId.getMostSignificantBits(), traceId.getLeastSignificantBits());
|
||||
Get get = new Get(uuidBytes);
|
||||
get.addFamily(COLFAM_SPAN);
|
||||
gets.add(get);
|
||||
}
|
||||
return template2.get(HBaseTables.TRACES, gets, spanMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<SpanBo>> selectSpans(Set<TraceId> traceIds) {
|
||||
List<Get> gets = new ArrayList<Get>(traceIds.size());
|
||||
for (TraceId traceId : traceIds) {
|
||||
Get get = new Get(traceId.getBytes());
|
||||
get.addFamily(COLFAM_SPAN);
|
||||
gets.add(get);
|
||||
}
|
||||
return template2.get(HBaseTables.TRACES, gets, spanMapper);
|
||||
}
|
||||
|
||||
public List<List<SpanBo>> selectSpansAndAnnotation(Set<TraceId> traceIds) {
|
||||
List<Get> gets = new ArrayList<Get>(traceIds.size());
|
||||
for (TraceId traceId : traceIds) {
|
||||
Get get = new Get(traceId.getBytes());
|
||||
get.addFamily(COLFAM_SPAN);
|
||||
get.addFamily(COLFAM_ANNOTATION);
|
||||
gets.add(get);
|
||||
}
|
||||
return template2.get(HBaseTables.TRACES, gets, spanAnnotationMapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.nhn.hippo.web.dao.hbase;
|
||||
|
||||
import com.nhn.hippo.web.dao.TraceIndexDao;
|
||||
import com.profiler.common.hbase.HBaseTables;
|
||||
import com.profiler.common.hbase.HbaseOperations2;
|
||||
import com.profiler.common.util.SpanUtils;
|
||||
import org.apache.hadoop.hbase.client.Scan;
|
||||
import org.apache.hadoop.hbase.util.Bytes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.hadoop.hbase.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Repository
|
||||
public class HbaseTraceIndexDao implements TraceIndexDao {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private final byte[] COLFAM_TRACE = Bytes.toBytes("Trace");
|
||||
private final byte[] COLNAME_ID = Bytes.toBytes("ID");
|
||||
|
||||
@Autowired
|
||||
private HbaseOperations2 hbaseOperations2;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("traceIndexMapper")
|
||||
private RowMapper<byte[]> traceIndexMapper;
|
||||
|
||||
private int scanCacheSize = 40;
|
||||
|
||||
public void setScanCacheSize(int scanCacheSize) {
|
||||
this.scanCacheSize = scanCacheSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<byte[]> scanTraceIndex(String agent, long start, long end) {
|
||||
Scan scan = createScan(agent, start, end);
|
||||
return hbaseOperations2.find(HBaseTables.TRACE_INDEX, scan, traceIndexMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<byte[]>> multiScanTraceIndex(String[] agents, long start, long end) {
|
||||
final List<Scan> multiScan = new ArrayList<Scan>(agents.length);
|
||||
for (String agent : agents) {
|
||||
Scan scan = createScan(agent, start, end);
|
||||
multiScan.add(scan);
|
||||
}
|
||||
return hbaseOperations2.find(HBaseTables.TRACE_INDEX, multiScan, traceIndexMapper);
|
||||
}
|
||||
|
||||
private Scan createScan(String agent, long start, long end) {
|
||||
|
||||
Scan scan = new Scan();
|
||||
// cache size를 지정해야 되는거 같음.??
|
||||
scan.setCaching(this.scanCacheSize);
|
||||
|
||||
byte[] bAgent = Bytes.toBytes(agent);
|
||||
byte[] traceIndexStartKey = SpanUtils.getTraceIndexRowKey(bAgent, start);
|
||||
scan.setStartRow(traceIndexStartKey);
|
||||
// TODO 추가 filter를 구현하여 scan시 중복된 값을 제가 할수 있음. 단 server에도 Filter 클래스가
|
||||
// 배포되어야 한다.
|
||||
// scan.setFilter(new ValueFilter());
|
||||
|
||||
byte[] traceIndexEndKey = SpanUtils.getTraceIndexRowKey(bAgent, end);
|
||||
scan.setStopRow(traceIndexEndKey);
|
||||
scan.addColumn(COLFAM_TRACE, COLNAME_ID);
|
||||
scan.setId("traceIndexScan");
|
||||
|
||||
// json으로 변화해서 로그를 찍어서. 최초 변환 속도가 느림.
|
||||
logger.debug("create scan:{}", scan);
|
||||
return scan;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,16 @@ public interface FlowChartService {
|
||||
*/
|
||||
public Set<TraceId> selectTraceIdsFromTraceIndex(String[] agentIds, long from, long to);
|
||||
|
||||
/**
|
||||
* select traceIds from ApplicationTraceIndex table
|
||||
*
|
||||
* @param agentIds
|
||||
* @param from
|
||||
* @param to
|
||||
* @return
|
||||
*/
|
||||
public Set<TraceId> selectTraceIdsFromApplicationTraceIndex(String applicationName, long from, long to);
|
||||
|
||||
/**
|
||||
* select call tree
|
||||
*
|
||||
@@ -52,4 +62,8 @@ public interface FlowChartService {
|
||||
* @return all of application names
|
||||
*/
|
||||
public List<String> selectAllApplicationNames();
|
||||
|
||||
|
||||
public String[] selectAgentIds(String[] hosts);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.nhn.hippo.web.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -13,12 +16,16 @@ import org.springframework.stereotype.Service;
|
||||
import com.nhn.hippo.web.calltree.rpc.RPCCallTree;
|
||||
import com.nhn.hippo.web.calltree.server.ServerCallTree;
|
||||
import com.nhn.hippo.web.dao.ApplicationIndexDao;
|
||||
import com.nhn.hippo.web.dao.ApplicationTraceIndexDao;
|
||||
import com.nhn.hippo.web.dao.RootTraceIndexDao;
|
||||
import com.nhn.hippo.web.dao.TraceDao;
|
||||
import com.nhn.hippo.web.dao.TraceIndexDao;
|
||||
import com.nhn.hippo.web.vo.TraceId;
|
||||
import com.profiler.common.bo.SpanBo;
|
||||
import com.profiler.common.hbase.HBaseClient;
|
||||
import com.profiler.common.hbase.HBaseQuery;
|
||||
import com.profiler.common.hbase.HBaseQuery.HbaseColumn;
|
||||
import com.profiler.common.hbase.HBaseTables;
|
||||
|
||||
/**
|
||||
* @author netspider
|
||||
@@ -44,6 +51,9 @@ public class FlowChartServiceImpl implements FlowChartService {
|
||||
@Autowired
|
||||
private ApplicationIndexDao applicationIndexDao;
|
||||
|
||||
@Autowired
|
||||
private ApplicationTraceIndexDao applicationTraceIndexDao;
|
||||
|
||||
@Override
|
||||
public List<String> selectAllApplicationNames() {
|
||||
return applicationIndexDao.selectAllApplicationNames();
|
||||
@@ -111,4 +121,44 @@ public class FlowChartServiceImpl implements FlowChartService {
|
||||
}
|
||||
return tree.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<TraceId> selectTraceIdsFromApplicationTraceIndex(String applicationName, long from, long to) {
|
||||
if (applicationName == null) {
|
||||
throw new NullPointerException("applicationName");
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("scan {}, {}, {}", new Object[] { applicationName, from, to });
|
||||
}
|
||||
|
||||
List<byte[]> bytes = this.applicationTraceIndexDao.scanTraceIndex(applicationName, from, to);
|
||||
Set<TraceId> result = new HashSet<TraceId>();
|
||||
for (byte[] traceId : bytes) {
|
||||
TraceId tid = new TraceId(traceId);
|
||||
result.add(tid);
|
||||
logger.trace("traceid:{}", tid);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] selectAgentIds(String[] hosts) {
|
||||
List<HbaseColumn> column = new ArrayList<HBaseQuery.HbaseColumn>();
|
||||
column.add(new HbaseColumn("Agents", "AgentID"));
|
||||
|
||||
HBaseQuery query = new HBaseQuery(HBaseTables.APPLICATION_INDEX, null, null, column);
|
||||
Iterator<Map<String, byte[]>> iterator = client.getHBaseData(query);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
while (iterator.hasNext()) {
|
||||
logger.debug("selectedAgentId={}", iterator.next());
|
||||
}
|
||||
logger.debug("!!!==============WARNING==============!!!");
|
||||
logger.debug("!!! selectAgentIds IS NOT IMPLEMENTED !!!");
|
||||
logger.debug("!!!===================================!!!");
|
||||
}
|
||||
|
||||
return hosts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#hbase.client.host=localhost
|
||||
hbase.client.host=10.25.131.38
|
||||
hbase.client.host=localhost
|
||||
#hbase.client.host=10.25.131.38
|
||||
hbase.client.port=2181
|
||||
hbase.htable.threads.max=64
|
||||
@@ -5,10 +5,10 @@
|
||||
"nodes" : [
|
||||
<c:forEach items="${nodes}" var="node" varStatus="status">
|
||||
<c:if test="${node.terminal}">
|
||||
{ "name" : "${node.endPoint}" }
|
||||
{ "name" : "${node.endPoint}", "applicationName" : "${node.endPoint}" }
|
||||
</c:if>
|
||||
<c:if test="${not node.terminal}">
|
||||
{ "name" : "${node}" }
|
||||
{ "name" : "${node}", "applicationName" : "${node.applicationName}" }
|
||||
</c:if>
|
||||
<c:if test="${!status.last}">,</c:if>
|
||||
</c:forEach>
|
||||
|
||||
@@ -99,8 +99,8 @@
|
||||
<select id="application">
|
||||
<option></option>
|
||||
</select>
|
||||
<input id="startdate" type="text" class="input-small" placeholder="StartDate" value="2012-10-29">
|
||||
<input id="starttime" type="text" class="input-small" placeholder="StartTime" value="12:00">
|
||||
<input id="startdate" type="text" class="input-small" placeholder="StartDate" value="2012-11-06">
|
||||
<input id="starttime" type="text" class="input-small" placeholder="StartTime" value="10:00">
|
||||
~
|
||||
<input id="enddate" type="text" class="input-small" placeholder="EndDate" value="2012-12-31">
|
||||
<input id="endtime" type="text" class="input-small" placeholder="EndTime" value="12:00">
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.profiler.common.dto.thrift.Span;
|
||||
import com.profiler.common.hbase.HBaseTables;
|
||||
import com.profiler.common.hbase.HbaseTemplate2;
|
||||
import com.profiler.common.util.SpanUtils;
|
||||
import com.profiler.server.dao.TraceDao;
|
||||
import com.profiler.server.dao.Traces;
|
||||
import org.apache.hadoop.hbase.client.Delete;
|
||||
import org.apache.thrift.TException;
|
||||
import org.junit.Before;
|
||||
@@ -30,7 +30,7 @@ public class SpanServiceTest {
|
||||
|
||||
|
||||
@Autowired
|
||||
private TraceDao traceDao;
|
||||
private Traces traceDao;
|
||||
|
||||
@Autowired
|
||||
private SpanService spanService;
|
||||
@@ -107,7 +107,7 @@ public class SpanServiceTest {
|
||||
|
||||
|
||||
private void insert(Span span) throws TException {
|
||||
traceDao.insert(span);
|
||||
traceDao.insert("JUNITApplicationName", span);
|
||||
}
|
||||
|
||||
AtomicInteger id = new AtomicInteger(0);
|
||||
|
||||
Reference in New Issue
Block a user