Initial implementation of profctl
authorAleksei Vereshchagin <avereschagin@dev.rtsoft.ru>
Fri, 1 Jun 2018 10:44:41 +0000 (13:44 +0300)
committerDmitri Botcharnikov <dmitry.b@samsung.com>
Mon, 18 Jun 2018 09:02:20 +0000 (12:02 +0300)
CMakeLists.txt [new file with mode: 0644]
packaging/profctl.manifest [new file with mode: 0644]
packaging/profctl.spec [new file with mode: 0644]
profctl.c [new file with mode: 0644]

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..84a8f5c
--- /dev/null
@@ -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 (file)
index 0000000..6ef6b12
--- /dev/null
@@ -0,0 +1,8 @@
+<manifest>
+    <request>
+        <domain name="_"/>
+    </request>
+    <assign>
+        <filesystem path="/usr/bin/profctl" label="User::Shell" exec_label="User"/>
+    </assign>
+</manifest>
diff --git a/packaging/profctl.spec b/packaging/profctl.spec
new file mode 100644 (file)
index 0000000..06340ce
--- /dev/null
@@ -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 (file)
index 0000000..b9e1f28
--- /dev/null
+++ b/profctl.c
@@ -0,0 +1,497 @@
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <getopt.h>
+#include <signal.h>
+#include <pthread.h>
+#include <termios.h>
+#include <aul/aul.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/xattr.h>
+#include <sys/statvfs.h>
+#include <fcntl.h>
+
+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;
+}
+