/* * Copyright 2016 the original author or authors. * @https://github.com/scouter-project/scouter * * Licensed 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 scouter.agent.batch.trace; import java.io.File; import java.lang.management.ManagementFactory; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.StringTokenizer; import scouter.agent.batch.Configure; import scouter.agent.batch.Logger; import scouter.lang.pack.BatchPack; import scouter.lang.pack.MapPack; import scouter.lang.value.BooleanValue; import scouter.lang.value.MapValue; import scouter.util.SysJMX; public class TraceContext { private static final String SQL_OTHERS = "Others"; private static final int SQL_OTHERS_HASH = SQL_OTHERS.hashCode(); private static TraceContext instance = null; public String batchJobId; public String args; public Integer pID; public long startTime; public long endTime; public int threadCnt = 0; public long startCpu; public long endCpu; public long gcTime= 0L; public long gcCount = 0L; public int sqlTotalCnt = 0; public long sqlTotalTime = 0L; public long sqlTotalRows = 0L; public long sqlTotalRuns = 0L; public boolean isStackLogFile = false; public String standAloneFile = null; private HashMap<Integer, String> uniqueSqls = new HashMap<Integer, String>(100); private HashMap<Integer, TraceSQL> sqlMap = new HashMap<Integer, TraceSQL>(100); private List<LocalSQL> localSQLList = new ArrayList<LocalSQL>(); private int sqlMaxCount; public String lastStack = null; static { instance = new TraceContext(); } final public static TraceContext getInstance(){ return instance; } private TraceContext() { startTime = ManagementFactory.getRuntimeMXBean().getStartTime(); readBatchId(); sqlMaxCount = Configure.getInstance().sql_max_count; pID = SysJMX.getProcessPID(); startCpu = SysJMX.getProcessCPU(); } private void readBatchId(){ Configure config = Configure.getInstance(); if("props".equals(config.batch_id_type)){ batchJobId = config.getValue(config.batch_id); }else{ args = System.getProperty("sun.java.command"); StringTokenizer token = new StringTokenizer(args, " "); if("args".equals(config.batch_id_type)){ int index = Integer.parseInt(config.batch_id); int currentIndex = -1; while(token.hasMoreTokens()){ if(currentIndex == index){ batchJobId = token.nextToken(); break; }else{ token.nextToken(); } currentIndex++; } }else if("class".equals(config.batch_id_type)){ if(token.hasMoreTokens()){ batchJobId = token.nextToken(); } } } if(batchJobId == null || batchJobId.length() == 0){ batchJobId ="NoId[Scouter]"; } Logger.println("Batch ID="+ batchJobId); } public int getSQLHash(String sqltext, int hashValue){ synchronized(uniqueSqls){ if(uniqueSqls.get(hashValue) != null){ return hashValue; } if(uniqueSqls.size() < sqlMaxCount){ uniqueSqls.put(hashValue, sqltext); return hashValue; }else if(uniqueSqls.size() == sqlMaxCount){ uniqueSqls.put(SQL_OTHERS_HASH, SQL_OTHERS); } return SQL_OTHERS_HASH; } } public HashMap<Integer, String> getUniqueSQLs(){ return uniqueSqls; } public String toString(){ StringBuilder buffer = new StringBuilder(100); String lineSeparator = System.getProperty("line.separator"); buffer.append("-[").append(this.batchJobId).append("]----------------------------------------------").append(lineSeparator); buffer.append("Run Command: ").append(this.args).append(lineSeparator); if(this.isStackLogFile){ buffer.append("Stack Dump: ").append(this.getLogFullFilename()).append(lineSeparator); } SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); buffer.append("Start Time: ").append(sdf.format(new Date(this.startTime))).append(lineSeparator); buffer.append("Stop Time: ").append(sdf.format(new Date(this.endTime))).append(lineSeparator); buffer.append("Elapsed Time: ").append((this.endTime - this.startTime)).append("ms").append(lineSeparator); if(this.getCPUTimeByMillis() > 0){ buffer.append("CPU Time: ").append(this.getCPUTimeByMillis()).append("ms").append(lineSeparator); } if(this.gcCount > 0){ buffer.append("GC Count: ").append(this.gcCount).append(lineSeparator); buffer.append("GC Time: ").append(this.gcTime).append("ms").append(lineSeparator); } if(sqlMap.size() > 0){ buffer.append("SQL Time: ").append((sqlTotalTime/1000000L)).append("ms").append(lineSeparator); buffer.append("SQL Type: ").append(sqlMap.size()).append(lineSeparator); buffer.append("SQL Runs: ").append(sqlTotalRuns).append(lineSeparator); } if(threadCnt > 0){ buffer.append("Thread Count: ").append(this.threadCnt).append(lineSeparator); } if(sqlMap.size() > 0){ buffer.append(lineSeparator).append("<SQLs>").append(lineSeparator); int index = 0; buffer.append("Index Runs TotalTime MinTime MaxTime Rows (Measured) StartTime EndTime").append(lineSeparator); buffer.append("--------------------------------------------------------------------------------------------------------------------------------------"); List<TraceSQL> list = sortTraceSQLList(); for(TraceSQL traceSql : list){ index++; buffer.append(lineSeparator); buffer.append(String.format("%5s", index)).append(' '); buffer.append(String.format("%,13d", traceSql.runs)).append(' '); buffer.append(String.format("%,13d", traceSql.getTotalTimeByMillis())).append(' '); buffer.append(String.format("%,13d", traceSql.getMinTimeByMillis())).append(' '); buffer.append(String.format("%,13d", traceSql.getMaxTimeByMillis())).append(' '); buffer.append(String.format("%,13d", traceSql.processedRows)).append(' ').append(String.format("%10s", traceSql.rowed)).append(' '); buffer.append(sdf.format(new Date(traceSql.startTime))).append(' '); buffer.append(sdf.format(new Date(traceSql.endTime))); } buffer.append(lineSeparator).append("--------------------------------------------------------------------------------------------------------------------------------------").append(lineSeparator); buffer.append(lineSeparator).append("<SQL Texts>").append(lineSeparator); index = 0; for(TraceSQL traceSql : list){ index++; buffer.append("-----------------").append(lineSeparator); buffer.append("#SQLINX-").append(index).append(lineSeparator); buffer.append(uniqueSqls.get(traceSql.hashValue)).append(lineSeparator); } } return buffer.toString(); } public void addSQLStats(LocalSQL localSql){ if(localSql == null || localSql.size() == 0){ return; } Integer hashValue; TraceSQL statsSql; for(TraceSQL sql : localSql.values()){ hashValue = sql.hashValue; synchronized(sqlMap){ statsSql = sqlMap.get(hashValue); if(statsSql == null){ statsSql = new TraceSQL(); statsSql.hashValue = hashValue; sqlMap.put(hashValue, statsSql); this.sqlTotalCnt++; } } synchronized(statsSql){ statsSql.runs += sql.runs; this.sqlTotalRuns += sql.runs; statsSql.totalTime += sql.totalTime; this.sqlTotalTime += sql.totalTime; statsSql.processedRows += sql.processedRows; this.sqlTotalRows += sql.processedRows; if( statsSql.startTime > sql.startTime || statsSql.startTime == -1L){ statsSql.startTime = sql.startTime; } if(statsSql.endTime < sql.endTime){ statsSql.endTime = sql.endTime; } if(statsSql.minTime > sql.minTime){ statsSql.minTime = sql.minTime; } if(statsSql.maxTime < sql.maxTime){ statsSql.maxTime = sql.maxTime; } if(sql.rowed){ statsSql.rowed = true; } } } } public void caculateLast(){ synchronized(localSQLList){ for(LocalSQL localSql : localSQLList){ addSQLStats(localSql); } localSQLList.clear(); } caculateResource(); } public void caculateResource(){ this.endCpu = SysJMX.getProcessCPU(); long [] gcInfo = SysJMX.getCurrentProcGcInfo(); this.gcCount = gcInfo[0]; this.gcTime = gcInfo[1]; } public MapPack caculateTemp(){ MapPack map = new MapPack(); map.put("batchJobId", this.batchJobId); map.put("args", this.args); map.put("pID", (long)this.pID); map.put("startTime", this.startTime); map.put("elapsedTime", (System.currentTimeMillis() - this.startTime)); map.put("cPUTime", (this.endCpu - startCpu)); map.put("gcCount", this.gcCount); map.put("gcTime", this.gcTime); long tempSqlTotalTime = this.sqlTotalTime; long tempSqlTotalRows = this.sqlTotalRows; long tempSqlTotalRuns = this.sqlTotalRuns; synchronized(localSQLList){ for(LocalSQL localSql : localSQLList){ for(TraceSQL sql : localSql.values()){ tempSqlTotalTime += sql.totalTime; tempSqlTotalRows += sql.processedRows; tempSqlTotalRuns += sql.runs; } } } map.put("sqlTotalTime", tempSqlTotalTime); map.put("sqlTotalRows", tempSqlTotalRows); map.put("sqlTotalRuns", tempSqlTotalRuns); if(this.lastStack == null){ map.put("lastStack", "None"); }else{ map.put("lastStack", this.lastStack); } return map; } public void checkThread(){ Thread thread; LocalSQL localSql; int inx; synchronized(localSQLList){ for(inx = localSQLList.size() - 1; inx >=0; inx--){ localSql = localSQLList.get(inx); thread = localSql.getThread(); if(!thread.isAlive()){ addSQLStats(localSql); localSQLList.remove(inx); } } } } public void addLocalSQL(LocalSQL localSql){ synchronized(localSQLList){ localSQLList.add(localSql); threadCnt++; } } public void removeLocalSQL(LocalSQL localSql){ synchronized(localSQLList){ localSQLList.remove(localSql); } addSQLStats(localSql); } public List<LocalSQL> getLocalSQLList(){ return localSQLList; } public long getCPUTimeByMicro(){ return ((endCpu - startCpu)/1000L); } public long getCPUTimeByMillis(){ return ((endCpu - startCpu)/1000000L); } public String getLogFullFilename(){ Date dt = new Date(startTime); String fileSeparator = System.getProperty("file.separator"); String date = new SimpleDateFormat("yyyyMMdd").format(dt); File dir = new File(new StringBuilder(100).append(Configure.getInstance().sfa_dump_dir.getAbsolutePath()).append(fileSeparator).append(date).toString()); if(!dir.exists()){ dir.mkdirs(); } return new StringBuilder(100).append(dir.getAbsolutePath()).append(fileSeparator).append(batchJobId).append('_').append(date).append('_').append(new SimpleDateFormat("HHmmss.SSS").format(dt)).append('_').append(pID).toString(); } public List<TraceSQL> sortTraceSQLList(){ List<TraceSQL> inList = new ArrayList<TraceSQL>(sqlMap.size() + 1); synchronized(sqlMap){ for(TraceSQL traceSql : sqlMap.values()){ inList.add(traceSql); } } Collections.sort(inList, new Comparator<TraceSQL>(){ @Override public int compare(TraceSQL o1, TraceSQL o2) { if(o1.totalTime < o2.totalTime){ return 1; }else if(o1.totalTime > o2.totalTime){ return -1; } return 0; } }); return inList; } public BatchPack makePack(){ BatchPack pack = new BatchPack(); Configure config = Configure.getInstance(); pack.objHash = config.getObjHash(); pack.objName = config.getObjName(); pack.objType = config.obj_type; pack.batchJobId = this.batchJobId; pack.batchJobId = this.batchJobId; pack.args = this.args; pack.pID = this.pID; pack.startTime = this.startTime; pack.elapsedTime = (this.endTime - this.startTime); pack.threadCnt = this.threadCnt; pack.cpuTime = (this.endCpu - this.startCpu); pack.sqlTotalCnt = this.sqlTotalCnt; pack.sqlTotalTime = this.sqlTotalTime; pack.sqlTotalRows = this.sqlTotalRows; pack.sqlTotalRuns = this.sqlTotalRuns; pack.isStack = isStackLogFile; if(this.sqlTotalCnt > 0){ pack.uniqueSqls = this.uniqueSqls; pack.sqlStats = new ArrayList<MapValue>((int)this.sqlTotalCnt); MapValue value; for(TraceSQL traceSql : sortTraceSQLList()){ value = new MapValue(); pack.sqlStats.add(value); value.put("hashValue", (long)traceSql.hashValue); value.put("runs", (long)traceSql.runs); value.put("startTime", traceSql.startTime); value.put("endTime", traceSql.endTime); value.put("totalTime", traceSql.totalTime); value.put("minTime", traceSql.minTime); value.put("maxTime", traceSql.maxTime); value.put("processedRows", traceSql.processedRows); value.put("rowed", new BooleanValue(traceSql.rowed)); } } return pack; } }