resource: process: Handle the case that target cannot support taskstats
[platform/core/system/pass.git] / src / resource / resource-process.c
1 /*
2  * PASS (Power Aware System Service) - Process Resource Driver
3  *
4  * Copyright (c) 2022 Samsung Electronics Co., Ltd.
5  *
6  * Licensed under the Apache License, Version 2.0 (the License);
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 #include <unistd.h>
20 #include <linux/taskstats.h>
21
22 #include <util/log.h>
23 #include <util/resource.h>
24 #include <util/kernel.h>
25
26 #include <resource-monitor/resource-monitor.h>
27
28 struct process_context {
29         pid_t tgid;
30         pid_t ppid;
31         pid_t pgid;
32         char comm[TS_COMM_LEN];
33         struct taskstats prev;
34         struct taskstats curr;
35         struct proc_map_info map_info;
36
37         u_int64_t prev_total_time;
38         double cpu_period;
39         int online_cpu;
40         u_int64_t total_memory;
41 };
42
43 static long jiffy;
44 static int taskstat_support = -1;
45
46 static int process_get_cpu_util(struct resource *res,
47                                 const struct resource_attribute *attr,
48                                 void *data)
49 {
50         struct process_context *ctx;
51         struct taskstats *prev, *curr;
52         double *util = (double *)data;
53
54         if (!res || !attr || !data)
55                 return -EINVAL;
56
57         ctx = get_resource_privdata(res);
58         if (!ctx)
59                 return -EINVAL;
60
61         if (!ctx->tgid) {
62                 _E("resource %s is not yet initialized\n",
63                                 get_resource_name(res));
64                 return -EINVAL;
65         }
66
67         prev = &ctx->prev;
68         curr = &ctx->curr;
69
70         if (ctx->cpu_period >= 1E-6) {
71                 *util = (double)(curr->ac_utime + curr->ac_stime);
72                 *util -= (double)(prev->ac_utime + prev->ac_stime);
73                 *util *= (jiffy / (ctx->cpu_period * 10000.0));
74                 *util = min(*util, 100.0);
75         }
76
77         return 0;
78 }
79
80 static int process_get_mem_attrs(struct resource *res,
81                                  const struct resource_attribute *attr,
82                                  void *data)
83 {
84         struct process_context *ctx;
85         struct taskstats *curr;
86         u_int64_t *mem = (u_int64_t *)data;
87
88         if (!res || !attr || !data)
89                 return -EINVAL;
90
91         ctx = get_resource_privdata(res);
92         if (!ctx)
93                 return -EINVAL;
94
95         if (!ctx->tgid) {
96                 _E("resource %s is not yet initialized\n",
97                                 get_resource_name(res));
98                 return -EINVAL;
99         }
100
101         switch (attr->id) {
102         case PROCESS_ATTR_MEM_VIRT:
103                 curr = &ctx->curr;
104                 *mem = (curr->virtmem * 1024) / curr->ac_stime;
105                 break;
106         case PROCESS_ATTR_MEM_RSS:
107                 curr = &ctx->curr;
108                 *mem = (curr->coremem * 1024) / curr->ac_stime;
109                 break;
110         case PROCESS_ATTR_MEM_RSS_PERCENT:
111         {
112                 double *percent = (double *)data;
113
114                 curr = &ctx->curr;
115                 *percent = (curr->coremem * 1024) / curr->ac_stime;
116                 *percent /= ctx->total_memory * 100.0;
117                 break;
118         }
119         case PROCESS_ATTR_MEM_PSS:
120                 *mem = (u_int64_t)ctx->map_info.pss;
121                 break;
122         case PROCESS_ATTR_MEM_SWAP:
123                 *mem = (u_int64_t)ctx->map_info.swap;
124                 break;
125         case PROCESS_ATTR_MEM_SWAP_PSS:
126                 *mem = (u_int64_t)ctx->map_info.swap_pss;
127                 break;
128         case PROCESS_ATTR_MEM_GPU:
129                 *mem = (u_int64_t)ctx->map_info.gpu_mem;
130                 break;
131         default:
132                 return -EINVAL;
133         }
134
135         return 0;
136 }
137
138 static int process_get_disk_attrs(struct resource *res,
139                                 const struct resource_attribute *attr,
140                                 void *data)
141 {
142         struct process_context *ctx;
143         struct taskstats *prev, *curr;
144         u_int64_t period;
145
146         if (!res || !attr || !data)
147                 return -EINVAL;
148
149         ctx = get_resource_privdata(res);
150         if (!ctx)
151                 return -EINVAL;
152
153         if (!ctx->tgid) {
154                 _E("resource %s is not yet initialized\n",
155                                 get_resource_name(res));
156                 return -EINVAL;
157         }
158
159         prev = &ctx->prev;
160         curr = &ctx->curr;
161
162         period = curr->ac_etime - prev->ac_etime;
163
164         switch (attr->id) {
165         case PROCESS_ATTR_DISK_READ_PER_SEC:
166                 *(double *)data = (double)((curr->read_bytes - prev->read_bytes) * 1024) / period;
167                 break;
168         case PROCESS_ATTR_DISK_WRITE_PER_SEC:
169                 *(double *)data = (double)((curr->write_bytes - prev->write_bytes) * 1024) / period;
170                 break;
171         default:
172                 return -EINVAL;
173         }
174         return 0;
175 }
176
177 static int process_get_context_data(struct resource *res,
178                                 const struct resource_attribute *attr,
179                                 void *data)
180 {
181         struct process_context *ctx;
182
183         if (!res || !attr || !data)
184                 return -EINVAL;
185
186         ctx = get_resource_privdata(res);
187         if (!ctx)
188                 return -EINVAL;
189
190         if (!ctx->tgid) {
191                 _E("resource %s is not yet initialized\n",
192                                 get_resource_name(res));
193                 return -EINVAL;
194         }
195
196         switch (attr->id) {
197         case PROCESS_ATTR_NAME:
198                 if (!taskstat_support && (strlen(ctx->comm) == 0)) {
199                         char *stat_fields[PROCESS_STAT_FIELD_MAX];
200                         char buffer[BUFF_MAX];
201                         int ret, len;
202
203                         ret = kernel_get_process_stat_fields(ctx->tgid, buffer,
204                                                              BUFF_MAX, stat_fields);
205                         if (ret < 0)
206                                 break;
207
208                         len = strlen(stat_fields[PROCESS_STAT_FIELD_COMM]) - 2/* '(', ')' */;
209                         len = (len < TS_COMM_LEN - 1) ? len : TS_COMM_LEN - 1;
210                         strncpy(ctx->comm, stat_fields[PROCESS_STAT_FIELD_COMM] + 1, len);
211                 }
212
213                 strncpy((char *)data, ctx->comm, TS_COMM_LEN);
214                 break;
215         case PROCESS_ATTR_PGID:
216                 if (ctx->pgid < 0) {
217                         char *stat_fields[PROCESS_STAT_FIELD_MAX];
218                         char buffer[BUFF_MAX];
219                         int ret;
220
221                         ret = kernel_get_process_stat_fields(ctx->tgid, buffer,
222                                                              BUFF_MAX, stat_fields);
223                         if (ret < 0)
224                                 break;
225
226                         ctx->pgid = atoi(stat_fields[PROCESS_STAT_FIELD_PGID]);
227                 }
228
229                 *((int *)data) = ctx->pgid;
230                 break;
231         case PROCESS_ATTR_PPID:
232                 if (ctx->ppid < 0) {
233                         char *stat_fields[PROCESS_STAT_FIELD_MAX];
234                         char buffer[BUFF_MAX];
235                         int ret;
236
237                         ret = kernel_get_process_stat_fields(ctx->tgid, buffer,
238                                                              BUFF_MAX, stat_fields);
239                         if (ret < 0)
240                                 break;
241
242                         ctx->ppid = atoi(stat_fields[PROCESS_STAT_FIELD_PPID]);
243                 }
244
245                 *((int *)data) = ctx->ppid;
246                 break;
247         }
248
249         return 0;
250 }
251
252 static bool process_check_taskstat_support(struct resource *resource,
253                                             const struct resource_attribute *attr)
254 {
255         return !!taskstat_support;
256 }
257
258 static bool process_check_gpu_support(struct resource *resource,
259                                             const struct resource_attribute *attr)
260 {
261         return kernel_check_gpu_support();
262 }
263
264 static const struct resource_attribute process_attrs[] = {
265         {
266                 .name   = "PROCESS_ATTR_CPU_UTIL",
267                 .id     = PROCESS_ATTR_CPU_UTIL,
268                 .type   = DATA_TYPE_DOUBLE,
269                 .ops    = {
270                         .get = process_get_cpu_util,
271                         .is_supported = process_check_taskstat_support,
272                 },
273         }, {
274                 .name   = "PROCESS_ATTR_MEM_VIRT",
275                 .id     = PROCESS_ATTR_MEM_VIRT,
276                 .type   = DATA_TYPE_UINT64,
277                 .ops    = {
278                         .get = process_get_mem_attrs,
279                         .is_supported = process_check_taskstat_support,
280                 },
281         }, {
282                 .name   = "PROCESS_ATTR_MEM_RSS",
283                 .id     = PROCESS_ATTR_MEM_RSS,
284                 .type   = DATA_TYPE_UINT64,
285                 .ops    = {
286                         .get = process_get_mem_attrs,
287                         .is_supported = process_check_taskstat_support,
288                 },
289         }, {
290                 .name   = "PROCESS_ATTR_MEM_RSS_PERCENT",
291                 .id     = PROCESS_ATTR_MEM_RSS_PERCENT,
292                 .type   = DATA_TYPE_DOUBLE,
293                 .ops    = {
294                         .get = process_get_mem_attrs,
295                         .is_supported = process_check_taskstat_support,
296                 },
297         }, {
298                 .name   = "PROCESS_ATTR_DISK_READ_PER_SEC",
299                 .id     = PROCESS_ATTR_DISK_READ_PER_SEC,
300                 .type   = DATA_TYPE_DOUBLE,
301                 .ops    = {
302                         .get = process_get_disk_attrs,
303                         .is_supported = process_check_taskstat_support,
304                 },
305         }, {
306                 .name   = "PROCESS_ATTR_DISK_WRITE_PER_SEC",
307                 .id     = PROCESS_ATTR_DISK_WRITE_PER_SEC,
308                 .type   = DATA_TYPE_DOUBLE,
309                 .ops    = {
310                         .get = process_get_disk_attrs,
311                         .is_supported = process_check_taskstat_support,
312                 },
313         }, {
314                 .name   = "PROCESS_ATTR_NAME",
315                 .id     = PROCESS_ATTR_NAME,
316                 .type   = DATA_TYPE_STRING,
317                 .ops    = {
318                         .get = process_get_context_data,
319                         .is_supported = resource_attr_supported_always,
320                 },
321         }, {
322                 .name   = "PROCESS_ATTR_PGID",
323                 .id     = PROCESS_ATTR_PGID,
324                 .type   = DATA_TYPE_INT,
325                 .ops    = {
326                         .get = process_get_context_data,
327                         .is_supported = resource_attr_supported_always,
328                 },
329         }, {
330                 .name   = "PROCESS_ATTR_PPID",
331                 .id     = PROCESS_ATTR_PPID,
332                 .type   = DATA_TYPE_INT,
333                 .ops    = {
334                         .get = process_get_context_data,
335                         .is_supported = resource_attr_supported_always,
336                 },
337         }, {
338                 .name   = "PROCESS_ATTR_MEM_PSS",
339                 .id     = PROCESS_ATTR_MEM_PSS,
340                 .type   = DATA_TYPE_UINT64,
341                 .ops    = {
342                         .get = process_get_mem_attrs,
343                         .is_supported = resource_attr_supported_always,
344                 },
345         }, {
346                 .name   = "PROCESS_ATTR_MEM_SWAP",
347                 .id     = PROCESS_ATTR_MEM_SWAP,
348                 .type   = DATA_TYPE_UINT64,
349                 .ops    = {
350                         .get = process_get_mem_attrs,
351                         .is_supported = resource_attr_supported_always,
352                 },
353         }, {
354                 .name   = "PROCESS_ATTR_MEM_SWAP_PSS",
355                 .id     = PROCESS_ATTR_MEM_SWAP_PSS,
356                 .type   = DATA_TYPE_UINT64,
357                 .ops    = {
358                         .get = process_get_mem_attrs,
359                         .is_supported = resource_attr_supported_always,
360                 },
361         }, {
362                 .name   = "PROCESS_ATTR_MEM_GPU",
363                 .id     = PROCESS_ATTR_MEM_GPU,
364                 .type   = DATA_TYPE_UINT64,
365                 .ops    = {
366                         .get = process_get_mem_attrs,
367                         .is_supported = process_check_gpu_support,
368                 },
369         },
370 };
371
372 #define saturatingSub(a, b) (a > b ? a - b : 0)
373
374 static u_int64_t get_total_cpu_time(void)
375 {
376         struct cpu_stat cpu_stat;
377         u_int64_t total_time;
378         int ret;
379
380         ret = kernel_get_total_cpu_stat(&cpu_stat);
381         if (ret < 0) {
382                 _E("failed to get cpu stat");
383                 return 0;
384         };
385
386         total_time = cpu_stat.user + cpu_stat.system + cpu_stat.nice + cpu_stat.idle;
387         total_time += cpu_stat.wait + cpu_stat.hard_irq + cpu_stat.soft_irq;
388
389         return total_time;
390 }
391
392 static int process_setup_tgid(struct resource *res,
393                                 const struct resource_control *ctrl,
394                                 const void *data)
395 {
396         struct process_context *ctx;
397         u_int64_t total_memory;
398         int ret;
399         bool include_gpu_mem;
400
401         if (!res || !ctrl)
402                 return -EINVAL;
403
404         ctx = get_resource_privdata(res);
405         if (!ctx)
406                 return -EINVAL;
407
408         include_gpu_mem = is_resource_attr_interested(res, PROCESS_GROUP_ATTR_MEM_GPU);
409         total_memory = ctx->total_memory;
410         memset(ctx, 0, sizeof(*ctx));
411
412         ctx->total_memory = total_memory;
413         ctx->tgid = (pid_t)(intptr_t)data;
414         ctx->prev_total_time = get_total_cpu_time();
415
416         /* update initial status */
417         if (taskstat_support) {
418                 ret = kernel_get_thread_group_taskstats(&ctx->curr, ctx->tgid, true);
419                 if (ret < 0)
420                         return ret;
421
422                 memcpy(ctx->comm, ctx->curr.ac_comm, strlen(ctx->curr.ac_comm) + 1);
423                 ctx->ppid = ctx->curr.ac_ppid;
424         } else {
425                 /*
426                  * if taskstat is not supported, comm, pgid and ppid is retrieved
427                  * from procfs status lazily.
428                  *
429                  * ctx->comm is now "\0" so it is treated as not initialized.
430                  */
431                 ctx->ppid = -1;
432         }
433
434         ctx->pgid = -1;
435
436         ret = kernel_get_thread_group_map_info(&ctx->map_info, ctx->tgid, include_gpu_mem);
437         if (ret < 0)
438                 return ret;
439
440         return 0;
441 }
442
443 static const struct resource_control process_ctrls[] = {
444         {
445                 .name = "PROCESS_CTRL_TGID",
446                 .id = PROCESS_CTRL_TGID,
447                 .ops = {
448                         .set = process_setup_tgid,
449                 },
450         },
451 };
452
453 static int process_prepare_update(struct resource *res)
454 {
455         struct process_context *ctx;
456         u_int64_t total_time;
457         int ret, online;
458         bool include_gpu_mem;
459
460         if (!res)
461                 return -EINVAL;
462
463         ctx = get_resource_privdata(res);
464         if (!ctx)
465                 return -EINVAL;
466
467         include_gpu_mem = is_resource_attr_interested(res, PROCESS_GROUP_ATTR_MEM_GPU);
468
469         if (taskstat_support) {
470                 memcpy(&ctx->prev, &ctx->curr, sizeof(struct taskstats));
471                 ret = kernel_get_thread_group_taskstats(&ctx->curr, ctx->tgid, false);
472                 if (ret < 0)
473                         return ret;
474         }
475
476         ret = kernel_get_thread_group_map_info(&ctx->map_info, ctx->tgid, include_gpu_mem);
477         if (ret < 0)
478                 return ret;
479
480         online = kernel_get_online_cpu_num();
481         total_time = get_total_cpu_time();
482
483         ctx->online_cpu = online;
484         ctx->cpu_period = (double)saturatingSub(total_time, ctx->prev_total_time) / online;
485         ctx->prev_total_time = total_time;
486
487         return 0;
488 }
489
490 static int process_init(struct resource *res)
491 {
492         struct process_context *ctx;
493         int ret;
494
495         if (jiffy == 0) {
496                 /* get system USER_HZ at once */
497                 jiffy = sysconf(_SC_CLK_TCK);
498                 if (jiffy < 0)
499                         return -EINVAL;
500         }
501
502         if (taskstat_support < 0)
503                 taskstat_support = (int)kernel_check_taskstat_support();
504
505         ctx = calloc(1, sizeof(struct process_context));
506         if (!ctx)
507                 return -ENOMEM;
508
509         ret = kernel_get_memory_total(&ctx->total_memory);
510         if (ret < 0) {
511                 free(ctx);
512                 return -EINVAL;
513         }
514
515         set_resource_privdata(res, ctx);
516
517         return 0;
518 }
519
520 static void process_exit(struct resource *res)
521 {
522         struct process_context *ctx;
523
524         if (!res)
525                 return;
526
527         ctx = get_resource_privdata(res);
528         if (!ctx)
529                 return;
530
531         free(ctx);
532         set_resource_privdata(res, NULL);
533 }
534
535 static const struct resource_driver process_resource_driver = {
536         .name           = "PROCESS",
537         .type           = RESOURCE_TYPE_PROCESS,
538         .flag           = RESOURCE_FLAG_PROCESS,
539         .attrs          = process_attrs,
540         .num_attrs      = ARRAY_SIZE(process_attrs),
541         .ctrls          = process_ctrls,
542         .num_ctrls      = ARRAY_SIZE(process_ctrls),
543         .ops = {
544                 .init = process_init,
545                 .exit = process_exit,
546                 .prepare_update = process_prepare_update,
547         },
548 };
549 RESOURCE_DRIVER_REGISTER(&process_resource_driver)