该文档主要是分析GT获取memory,cpu,network数据的方案,主要是读取系统虚拟文件/proc,如需深入了解可以看Linux Programmer's Manual proc参考文档
-
内存获取:
- GT核心代码:com.tencent.wstt.gt.collector.monitor.NormalMonitor
public static int getMemory_app(int pid, Context context) { ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); int[] myMempid = new int[] { pid }; assert am != null; Debug.MemoryInfo[] memoryInfo = am.getProcessMemoryInfo(myMempid); return memoryInfo[0].getTotalPss(); }
-
时序图
-
第4步读取/proc/pid/smaps内容:
f61e3000-f61e7000 rw-p 00000000 00:05 9015 /dev/ashmem/dalvik-thread local mark stack (deleted)
Size: 16 kB
Rss: 0 kB
Pss: 0 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 0 kB
Anonymous: 0 kB
AnonHugePages: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
VmFlags: rd wr mr mw me ac
字段含义:
第几个 | 字段名 | 含义 |
---|---|---|
1 | 地址空间的开始地址 - 结束地址 | |
2 | Size | 是进程使用内存空间,并不一定实际分配了物理内存 |
3 | Rss | 实际驻留”在内存中”的内存数. 不包括已经交换出去的页面。RSS还包括了与其它进程共享的内存区域,通常用于共享库; |
4 | Pss | Rss中私有的内存页面 |
5 | Shared_Clean | Rss中和其他进程共享的未改写页面 |
6 | Shared_Dirty | Rss中和其他进程共享的已改写页面 |
7 | Private_Clean | Rss中改写的私有页面页面 |
8 | Private_Dirty | Rss中已改写的私有页面页面 |
参考文档
Android系统相关核心代码:
//object就是java层传递进来接收数据的MemoryInfo对象
248 static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz,
249 jint pid, jobject object)
250 {
251 stats_t stats[_NUM_HEAP];
252 memset(&stats, 0, sizeof(stats));
254 load_maps(pid, stats);
.....
283 }
235 static void load_maps(int pid, stats_t* stats)
236 {
237 char tmp[128];
238 FILE *fp;
239
240 sprintf(tmp, "/proc/%d/smaps", pid);
241 fp = fopen(tmp, "r");
242 if (fp == 0) return;
244 read_mapinfo(fp, stats);
245 fclose(fp);
246 }
//逐行读取文件
121 static void read_mapinfo(FILE *fp, stats_t* stats)
......
144 while (!done) {
......
154 if (sscanf(line, "%lx-%lx %*s %*x %*x:%*x %*d%n", &start, &end, &nam e_pos) != 2) {
155 skip = true;
156 } else {
157 while (isspace(line[name_pos])) {
158 name_pos += 1;
159 }
160 name = line + name_pos;
161 nameLen = strlen(name);
162
163 if ((strstr(name, "[heap]") == name) ||
164 (strstr(name, "/dev/ashmem/libc malloc") == name)) {
165 whichHeap = HEAP_NATIVE;
166 } else if (strstr(name, "/dev/ashmem/dalvik-") == name) {
167 whichHeap = HEAP_DALVIK;
168 } else if (strstr(name, "[stack") == name) {
169 whichHeap = HEAP_STACK;
170 } else if (strstr(name, "/dev/ashmem/CursorWindow") == name) {
171 whichHeap = HEAP_CURSOR;
172 } else if (strstr(name, "/dev/ashmem/") == name) {
173 whichHeap = HEAP_ASHMEM;
174 } else if (strstr(name, "/dev/") == name) {
175 whichHeap = HEAP_UNKNOWN_DEV;
176 } else if (nameLen > 3 && strcmp(name+nameLen-3, ".so") == 0) {
177 whichHeap = HEAP_SO;
178 } else if (nameLen > 4 && strcmp(name+nameLen-4, ".jar") == 0) {
179 whichHeap = HEAP_JAR;
180 } else if (nameLen > 4 && strcmp(name+nameLen-4, ".apk") == 0) {
181 whichHeap = HEAP_APK;
182 } else if (nameLen > 4 && strcmp(name+nameLen-4, ".ttf") == 0) {
183 whichHeap = HEAP_TTF;
184 } else if ((nameLen > 4 && strcmp(name+nameLen-4, ".dex") == 0) ||
185 (nameLen > 5 && strcmp(name+nameLen-5, ".odex") == 0) ) {
186 whichHeap = HEAP_DEX;
187 } else if (nameLen > 0) {
188 whichHeap = HEAP_UNKNOWN_MAP;
189 } else if (start == prevEnd && prevHeap == HEAP_SO) {
190 // bss section of a shared library.
191 whichHeap = HEAP_SO;
192 }
193 }
......
198 while (true) {
199 if (fgets(line, 1024, fp) == 0) {
200 done = true;
201 break;
202 }
203
204 if (sscanf(line, "Size: %d kB", &temp) == 1) {
205 size = temp;
206 } else if (sscanf(line, "Rss: %d kB", &temp) == 1) {
207 resident = temp;
208 } else if (sscanf(line, "Pss: %d kB", &temp) == 1) {
209 pss = temp;
210 } else if (sscanf(line, "Shared_Clean: %d kB", &temp) == 1) {
211 shared_clean = temp;
212 } else if (sscanf(line, "Shared_Dirty: %d kB", &temp) == 1) {
213 shared_dirty = temp;
214 } else if (sscanf(line, "Private_Clean: %d kB", &temp) == 1) {
215 private_clean = temp;
216 } else if (sscanf(line, "Private_Dirty: %d kB", &temp) == 1) {
217 private_dirty = temp;
218 } else if (sscanf(line, "Referenced: %d kB", &temp) == 1) {
219 referenced = temp;
220 } else if (strlen(line) > 30 && line[8] == '-' && line[17] == ' ') {
221 // looks like a new mapping
222 // example: "10000000-10001000 ---p 10000000 00:00 0"
223 break;
224 }
225 }
......
232 }
233 }
-
读取一个进程cpu数据
- GT核心代码:
public static long getCPU_app(int pid) { Scanner scanner = null; long cpuApp = 0; try { scanner = new Scanner(new File("/proc/" + pid + "/stat")); int i = 0; while (scanner.hasNext() && i < 13) { scanner.next(); i++; } //这里是 usage_in_time_units = utime + stime + cutime + cstime; cpuApp = scanner.nextLong() + scanner.nextLong() + scanner.nextLong() + scanner.nextLong(); } catch (IOException e) { e.printStackTrace(); } finally { if (scanner != null) { scanner.close(); } } return cpuApp; }
- 读取/proc/pid/stat内容:
9473 (tencent.wstt.gt) S 1715 1715 0 0 -1 1077952832 20672 14376 36 0 2573 869 0 0 20 0 36 0 1245958 1516355584 17253 18446744073709551615 1585926144 1585942752 4292355520 4292344336 4131982169 0 4612 1 1073775864 0 0 0 17 3 0 0 1 0 0 1585950004 1585950720 1606168576 4292356986 4292357062 4292357062 4292358112 0
第几个 | 字段名 | 含义 |
---|---|---|
1 | pid | 进程号 |
2 | comm | 应用程序或命令的名字 |
S | state | 任务的状态,R:runnign, S:sleeping (TASK_INTERRUPTIBLE), D:disk sleep (TASK_UNINTERRUPTIBLE), T: stopped, T:tracing stop,Z:zombie, X:dead |
14 | utime | 用户态运行的时间,包含guest time |
15 | stime | 内核态运行的时间 |
16 | cutime | 该进程spawn的子进程在用户态消耗的jiffies |
17 | cstime | 该进程spawn的子进程在内核态消耗的jiffies |
process jiffies是内核提供的该进程在DeltaT时间内消耗的jiffies
process jiffies = utime + stime + cutime + cstime
参考文章 进程cpu使用率的计算
-
读取/proc/stat系统cpu数据
cpu 165726 66054 180319 10448201 5747 0 4368 0 0 0
cpu0 39122 16057 46328 2596897 853 0 3906 0 0 0
cpu1 40228 16678 44921 2619101 1723 0 182 0 0 0
cpu2 41079 16139 46316 2613505 1646 0 159 0 0 0
cpu3 45297 17180 42754 2618698 1525 0 121 0 0 0
intr 17549673 16 0 0 0 7 0 0 0 1 0 284778 119089 0 0 0 0 1 4526 1282982 1 0 102878 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 35200542
btime 1535412445
processes 67824
procs_running 1
procs_blocked 0
softirq 10561214 275826 2046001 36060 46525 0 0 4188132 1910413 0 2058257
第几个 | 字段名 | 含义 |
---|---|---|
1 | user | 用户态消耗的时间,不包含 nice值为负进程 |
2 | nice | 用户态消耗的时间,nice值为负的进程所占用的CPU时间 |
3 | system | 内核态运行的时间 |
4 | idle | 除IO等待时间以外的其它等待时间iowait |
5 | iowait | IO等待时间(不可靠) |
6 | irq | 硬中断时间 |
7 | softirq | 软中断时间 |
8 | steal | 虚拟化环境中运行时在其他操作系统上花费的时间 |
算法:
- 系统占用cpu时间
int64_t load = user + nice + system + irq + softirq + steal; - 总流失时间
int64_t elapsed = load + idle + iowait;
-
读取线程占cpu用时间:
/proc/[pid]/task/[tid]/stat 参数含义同/proc/[pid]/stat
获取流量方式:
GT核心代码:
long flowUpload = TrafficStats.getUidTxBytes(android.os.Process.myUid()); long flowDownload = TrafficStats.getUidRxBytes(android.os.Process.myUid());
-
读取系统文件:/proc/net/xt_qtaguid/stats 内容 (这个文件记录所有uid的数据,可以根据uid_tag_int字段进行过滤)
idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
2 wlan0 0x0 0 0 32685 494 17136 261 8597 184 0 0 24088 310 2412 46 0 0 14724 215
61 radio0 0x0 0 0 15020 196 5132 79 1964 49 0 0 13056 147 1080 19 0 0 4052 60
70 lo 0x0 0 0 0 0 200 5 0 0 0 0 0 0 0 0 0 0 200 5
字段名 | 含义 |
---|---|
idx | 文件头 |
iface | 网络接口 |
acct_tag_hex | socket |
uid_tag_int | 是UID |
cnt_set | 标志位,0代表前台流量,1代表后台流量 |
rx_bytes | 接收数据 |
tx_bytes | 传输数据 |
-
Android中android_net_TrafficStats.cpp中获取uid流量的算法:
121 static int parseUidStats(const uint32_t uid, struct Stats* stats) {
122 FILE *fp = fopen(QTAGUID_UID_STATS, "r");
123 if (fp == NULL) {
124 return -1;
125 }
126
127 char buffer[384];
128 char iface[32];
129 uint32_t idx, cur_uid, set;
130 uint64_t tag, rxBytes, rxPackets, txBytes, txPackets;
131
132 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
133 if (sscanf(buffer, "%d %31s 0x%llx %u %u %llu %llu %llu %llu", &idx,
134 iface, &tag, &cur_uid, &set, &rxBytes, &rxPackets, &txBytes,
135 &txPackets) == 9) {
136 if (uid == cur_uid && tag == 0L) {
137 stats->rxBytes += rxBytes;
138 stats->rxPackets += rxPackets;
139 stats->txBytes += txBytes;
140 stats->txPackets += txPackets;
141 }
142 }
143 }
144
145 if (fclose(fp) != 0) {
146 return -1;
147 }
148 return 0;
149 }
GT存在的问题:
1.只能获取主进程数据,默认收集多种数据
2.内存数据没有细分,只返回来memoryInfo[0].getTotalPss(),流量数据只有上传下载的总量,不能细化到每个请求,IP
3.植入sdk会导致主进程性能数据偏高
解决方案:
1.可以添加一个broadcast广播或server端来接收切换进程的请求
2.1)内存数据可以返回memoryInfo,后续可以根据需要提取。
2)流量数据可以考虑hook请求class达到细化分类(方案待定)
3.sdk是java层做性能收集及密集计算,可以考虑在Native中处理,减小对主进程影响(方案待定)