From: Aleksei Vereshchagin Date: Fri, 1 Jun 2018 10:44:41 +0000 (+0300) Subject: Initial implementation of profctl X-Git-Tag: submit/tizen/20180619.082107~1 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=21356715a069dd48ca0afa7bb2c772c0511b9048;p=sdk%2Ftools%2Fprofctl.git Initial implementation of profctl --- diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..84a8f5c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,32 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +PROJECT("profctl") + +MESSAGE("CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") + +FOREACH(flag ${${PROJECT_NAME}_CFLAGS}) + SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) + +SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -pthread -std=c++11 -ggdb") +#SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fvisibility=hidden") +SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fdata-sections -ffunction-sections") +SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -D_FILE_OFFSET_BITS=64") + +SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}") +SET(CMAKE_CXX_FLAGS_DEBUG "-O2 -g") +SET(CMAKE_CXX_FLAGS_RELEASE "-O2") + +SET(PROFCTL "profctl") +SET(${PROFCTL}_SOURCE_FILES + profctl.c +) +ADD_EXECUTABLE(${PROFCTL} ${${PROFCTL}_SOURCE_FILES}) +SET_TARGET_PROPERTIES(${PROFCTL} PROPERTIES COMPILE_FLAGS "-fPIE") +TARGET_LINK_LIBRARIES(${PROFCTL} aul) +TARGET_LINK_LIBRARIES(${PROFCTL} ${${PROJECT_NAME}_LDFLAGS} "-pie -lpthread") + +SET_TARGET_PROPERTIES(${PROFCTL} + PROPERTIES SKIP_BUILD_RPATH TRUE +) # remove rpath option that is automatically generated by cmake. + +INSTALL(TARGETS ${PROFCTL} DESTINATION ${BINDIR}) diff --git a/packaging/profctl.manifest b/packaging/profctl.manifest new file mode 100644 index 0000000..6ef6b12 --- /dev/null +++ b/packaging/profctl.manifest @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/packaging/profctl.spec b/packaging/profctl.spec new file mode 100644 index 0000000..06340ce --- /dev/null +++ b/packaging/profctl.spec @@ -0,0 +1,41 @@ +%{!?buildtype: %define buildtype Release} + +Name: profctl +Summary: Utility to control profiling applications +Version: 1.0.0 +Release: 1 +Group: Development/Toolchain +License: Apache-2.0 +Source0: %{name}-%{version}.tar.gz +Source1000: profctl.manifest + +ExcludeArch: aarch64 + +BuildRequires: cmake +BuildRequires: aul-devel + +AutoReqProv: no +Requires: aul + +%description +Utility to control profiling applications + +%prep +%setup -q +cp %{SOURCE1000} . + +%build + +cmake \ + -DCMAKE_INSTALL_PREFIX=%{_prefix} \ + -DBINDIR=%{_bindir} \ + -DCMAKE_BUILD_TYPE=%{buildtype} +make %{?jobs:-j%jobs} VERBOSE=1 + +%install +rm -rf %{buildroot} +%make_install + +%files +%manifest profctl.manifest +%{_bindir}/profctl diff --git a/profctl.c b/profctl.c new file mode 100644 index 0000000..b9e1f28 --- /dev/null +++ b/profctl.c @@ -0,0 +1,497 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct app_info { + char *appid; + int pid; +}; + +static int verbose = 0; +static int doinfo = 0; +static int timeout = 1; +static int pid = -1; +static struct termios sterm; + +static char *appid = NULL; +static char *pname = NULL; +static char *ename = NULL; +static const char *oname = NULL; + +static struct option long_options[] = { + {"appid", required_argument, 0, 'a'}, + {"pipe", required_argument, 0, 'p'}, + {"exec", required_argument, 0, 'e'}, + {"error", required_argument, 0, 'o'}, + {"timeout", required_argument, 0, 't'}, + {"verbose", no_argument, 0, 'v'}, + {"list", no_argument, 0, 'l'}, + {"info", no_argument, 0, 'i'}, + {0, 0, 0, 0} +}; + +static FILE *tf; + +static void *output_thread(void *arg) +{ + size_t lsize = 0; + char *buffer = NULL; + + while (getline(&buffer, &lsize, tf) != -1) { + puts(buffer); + } + free(buffer); + + exit(0); +} + +static void *wait_thread(void *arg) +{ + while (aul_app_get_status_bypid(pid) >= 0) + sleep(1); + exit(0); +} + +static void finish() +{ + tcsetattr(0,TCSANOW, &sterm); + if (tf != NULL) { + fclose(tf); + unlink(pname); + } + kill(getpid(), SIGKILL); +} + +static void split(char *line, char **array, int n) +{ + int i; + + for (i = 0; i < n; i++) { + while(line[0] == ' ') *line++ = 0; + array[i] = line; + line = index(line, ' '); + if (line == NULL) + break; + *line++ = 0; + } + i++; + if (i < n) { + int k; + + for (k = 0; k <= i; k++) { + printf("%d == %s\n", k, array[k]); + } + } + for (; i < n; i++) { + array[i] = NULL; + } +} + +static void *outstat(void *arg) +{ + + char *sline = NULL; + char *pline = NULL; + char *mtline = NULL; + char *mfline = NULL; + size_t slen = 0; + size_t plen = 0; + size_t mtlen = 0; + size_t mflen = 0; + FILE *sstat = fopen("/proc/stat","r"); + FILE *pstat; + FILE *pmstat; + FILE *tf = fopen("/sys/devices/system/cpu/present","r"); + int tmp, ncpu; + char *pidname; + long psize = sysconf(_SC_PAGESIZE); + struct statvfs fstat; + + printf("psize %ld\n", psize); + + if (sstat == NULL ) { + perror("open /proc/stat"); + exit(1); + } + + if (tf == NULL ) { + perror("open /sys/devices/system/cpu/present"); + exit(1); + } + + fscanf(tf, "%d-%d\n", &tmp, &ncpu); + fclose(tf); + ncpu = ncpu - tmp + 1; + + tf = fopen("/proc/meminfo", "r"); + if (tf == NULL ) { + perror("open /proc/meminfo"); + exit(1); + } + + + if (asprintf(&pidname, "/proc/%d/statm", pid) == -1) { + perror("asprintf"); + exit(1); + } + + pmstat = fopen(pidname, "r"); + if (pmstat == NULL) { + perror("open /proc/$pid/statm"); + exit(1); + } + + pidname[strlen(pidname) - 1] = 0; + pstat = fopen(pidname, "r"); + if (pstat == NULL) { + perror("open /proc/$pid/stat"); + exit(1); + } + free(pidname); + + while(1) { + time_t t = time(NULL); + char *stats[5]; + char *pstats[18]; + char *mt[3]; + char *mf[3]; + long pages; + long available; + + fseek(sstat, 0, SEEK_SET); + fflush(sstat); + slen = getline(&sline, &slen, sstat); + if (slen == -1) + exit(1); + + split(sline, stats, 5); + + fseek(pstat, 0, SEEK_SET); + fflush(pstat); + plen = getline(&pline, &plen, pstat); + if (plen == -1) + exit(1); + + split(pline, pstats, sizeof(pstats) / sizeof(pstats[0])); + + fseek(pmstat, 0, SEEK_SET); + fflush(pmstat); + fscanf(pmstat, "%ld", &pages); + + fseek(tf, 0, SEEK_SET); + fflush(tf); + mtlen = getline(&mtline, &mtlen, tf); + mflen = getline(&mfline, &mflen, tf); + if (mtlen == -1 || mflen == -1) + exit(1); + + split(mtline, mt, 3); + split(mfline, mf, 3); + + if (statvfs(".", &fstat) < 0) { + perror("statfs"); + exit(1); + } + + available = (fstat.f_bsize >= 1024) + ? (fstat.f_bsize / 1024) * fstat.f_bavail + : fstat.f_bavail / (1024 / fstat.f_bsize); + + printf("%ld %d" " %s %s %s" " %s %s" " %s %s %ld %ld\n", + t, ncpu, + stats[1], stats[3], stats[4], /* user system idle */ + pstats[14 - 1], pstats[15 - 1], /* puser psystem */ + mt[1], mf[1], available, pages * psize); + + sleep(timeout); + } + return NULL; +} + + +static void CheckValue(char **value, const char *info) +{ + if (value[0] != NULL) { + fprintf(stderr, "%s is already defined to %s\n", info, *value); + exit(1); + } + value[0] = optarg; +} + +static int output_app_info(const aul_app_info *info, void *data) +{ + if (info == NULL || info->appid == NULL) + return -1; + fprintf(stderr, "pid %d status %d appid %s\n", + info->pid, info->status, info->appid); + return 0; +} + +static int ListApps(int n) +{ + aul_app_get_all_running_app_info(output_app_info, NULL); + exit(0); +} + +static int process_option(int argc, char **argv) +{ + int option_index; + + switch(getopt_long(argc, argv, "-a:p:vle:o:it:", + long_options, &option_index)) { + case 1: + if (appid == NULL) { + appid = optarg; + } else if (pname == NULL) { + pname = optarg; + } else { + fprintf(stderr, "Extra argument %s\n", optarg); + exit(1); + } + break; + case 'a': CheckValue(&appid, "Appid"); break; + case 'p': CheckValue(&pname, "pipe name"); break; + case 'e': CheckValue(&ename, "exe name"); break; + case 'o': CheckValue(&oname, "error name"); break; + case 'v': verbose++; break; + case 'l': ListApps(0); exit(0); + case 'i': doinfo = 1; break; + case 't': timeout = atoi(optarg); break; + default: + return -1; + } + return 0; +} + +static void SimpleThread(void* (*func)(void *)) +{ + pthread_attr_t attr; + pthread_t thread; + + if (pthread_attr_init(&attr)) { + perror("pthread_attr_init"); + exit(1); + } + + if (pthread_create(&thread, &attr, func, NULL)) { + perror("pthread_create"); + exit(1); + } +} + + +static int find_pid(const aul_app_info *info, void *data) +{ + if (!strcmp(appid, info->appid)) { + *(int*)data = info->pid; + return -1; + } + return 0; +} + +static void waitappid(int n) +{ + if (appid == NULL) + exit(1); + + for (; n > 0; n--) { + if (aul_app_is_running(appid)) { + int npid = 0; + aul_app_get_all_running_app_info(find_pid, &npid); + if (npid) { + SimpleThread(&wait_thread); + pid = npid; + return; + } + } + sleep(1); + } + exit(1); +} + +#if 1 +static int app_launch_handler(int npid, const char *app_id, void *data) +{ + if (verbose) { + fprintf(stderr, "app_launch_handler %d - %s\n", npid, app_id); + } + + if (pid > 0 || strcmp(app_id, appid)) + return 0; + + pid = npid; + return 1; +} + +static int app_dead_handler(int npid, void *data) +{ + if (verbose) { + fprintf(stderr, "app_dead_handler %d\n", npid); + } + + if (npid != pid) + return 0; + + sleep(1); + exit(0); + return 1; +} +#endif + +static void openFileProcess() +{ + int pipefd[2]; + int pid; + int id; + + if (ename == NULL) { + tf = fopen(pname, "r"); + if (tf == NULL) { + perror("fopen"); + exit(1); + } + return; + } + + if (access(ename, X_OK)) { + perror("access"); + exit(1); + } + + /* We must start interpreter process */ + if (pipe(pipefd)) { + perror("pipe"); + exit(1); + } + + id = open(pname, O_RDONLY); + if (id < 0) { + perror("open"); + exit(1); + } + + pid = fork(); + if (pid == -1) { + perror("fork"); + exit(1); + } + + if (pid == 0) { /* Child */ + close(pipefd[0]); + dup2(pipefd[1], 1); /* stdout */ + dup2(id, 0); /* stdin */ + execl(ename, ename, NULL); + perror("execl"); + exit(1); + } + + /* Parent */ + close(pipefd[1]); + close(id); + + tf = fdopen(pipefd[0], "r"); + if (tf == NULL) { + perror("fopen"); + exit(1); + } +} + +int main(int argc, char **argv) +{ + struct termios term; + char *line = NULL; + size_t len = 0; + + while(!process_option(argc, argv)); + + if (appid == NULL) { + fprintf(stderr, "Unknown app id\n"); + exit(1); + } + + if (oname == NULL) { + oname = "/tmp/profctl.log"; + } + + freopen(oname, "w", stderr); + setbuf(stderr, NULL); + + if (pname) { + unlink(pname); + if (mkfifo(pname, 0666)) { + perror("mkfifo"); + exit(1); + } + /* This may be useful for "root on" start */ + if (setxattr(pname, "security.SMACK64", "User::App::Shared", + strlen("User::App::Shared"),0)) { + perror("setattr"); + } + } + + /* stty */ + tcgetattr(0, &term); + sterm = term; + + term.c_lflag &= ~(ECHO|ECHONL); /* no echo and so on */ + term.c_oflag &= ~OPOST; /* no additional CR and so on */ + + tcsetattr(0,TCSANOW, &term); + + atexit(finish); + + if (pname) { + openFileProcess(); + SimpleThread(&output_thread); + } + +#if 1 + aul_listen_app_launch_signal_v2(app_launch_handler, NULL); + aul_listen_app_dead_signal(app_dead_handler, NULL); +#endif + + /* Read command loop */ + while((len = getline(&line, &len, stdin)) != -1) { + if (verbose) { + fprintf(stderr, "line :%s#\n", line); + } + if ((pid != -1) && !strncmp(line, "exit", 4)) { + kill(pid, SIGHUP); + break; + } else if ((pid != -1) && !strncmp(line, "kill", 4)) { + if (kill(pid, SIGINT)) + perror("kill"); + } else if ((pid != -1) && !strncmp(line, "start", 5)) { + if (kill(pid, SIGRTMIN+8)) + perror("kill"); + } else if ((pid != -1) && !strncmp(line, "stop", 4)) { + if (kill(pid, SIGRTMIN+7)) + perror("kill"); + } else if (!strncmp(line, "test", 4) && (pid == -1)) { + waitappid(10); /* try 10 seconds */ + if (verbose) { + fprintf(stderr, "pid = %d\n", pid); + } + if (doinfo) { + SimpleThread(&outstat); + } + } + } + + return 0; +} +