1/*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "bootchart.h"
17
18#include <dirent.h>
19#include <stdint.h>
20#include <sys/utsname.h>
21#include <sys/time.h>
22#include <time.h>
23#include <unistd.h>
24
25#include "init_module_engine.h"
26#include "init_param.h"
27#include "init_utils.h"
28#include "plugin_adapter.h"
29#include "securec.h"
30
31#define NANO_PRE_JIFFY 10000000
32#define BOOTCHART_OUTPUT_PATH "/data/service/el0/startup/init/"
33
34static BootchartCtrl *g_bootchartCtrl = NULL;
35
36BOOTCHART_STATIC long long GetJiffies(void)
37{
38    struct timespec time1 = {0};
39    clock_gettime(CLOCK_MONOTONIC, &time1);
40    long long jiffies1 = (long long)time1.tv_nsec / NANO_PRE_JIFFY;
41    long long jiffies2 = (long long)time1.tv_sec * (1000000000 / NANO_PRE_JIFFY); // 1000000000 to nsec
42    return jiffies1 + jiffies2;
43}
44
45char *ReadFileToBuffer(const char *fileName, char *buffer, uint32_t bufferSize)
46{
47    PLUGIN_CHECK(buffer != NULL && fileName != NULL, return NULL, "Invalid param");
48    int fd = -1;
49    ssize_t readLen = 0;
50    do {
51        buffer[0] = '\0';
52        errno = 0;
53        fd = open(fileName, O_RDONLY);
54        if (fd > 0) {
55            readLen = read(fd, buffer, bufferSize - 1);
56        }
57        PLUGIN_CHECK(readLen >= 0, break, "Failed to read data for %s %d readLen %d", fileName, errno, readLen);
58        buffer[readLen] = '\0';
59    } while (0);
60    if (fd != -1) {
61        close(fd);
62    }
63    return (readLen > 0) ? buffer : NULL;
64}
65
66BOOTCHART_STATIC void BootchartLogHeader(void)
67{
68    char date[32]; // 32 data size
69    time_t tm = time(NULL);
70    PLUGIN_CHECK(tm >= 0, return, "Failed to get time");
71    struct tm *now = localtime(&tm);
72    PLUGIN_CHECK(now != NULL, return, "Failed to get local time");
73    size_t size = strftime(date, sizeof(date), "%F %T", now);
74    PLUGIN_CHECK(size > 0, return, "Failed to strftime");
75    struct utsname uts;
76    if (uname(&uts) == -1) {
77        return;
78    }
79
80    char release[PARAM_VALUE_LEN_MAX] = {};
81    uint32_t len = sizeof(release);
82    (void)SystemReadParam("const.ohos.releasetype", release, &len);
83    char *cmdLine = ReadFileToBuffer("/proc/cmdline", g_bootchartCtrl->buffer, g_bootchartCtrl->bufferSize);
84    PLUGIN_CHECK(cmdLine != NULL, return, "Failed to open file "BOOTCHART_OUTPUT_PATH"header");
85
86    FILE *file = fopen(BOOTCHART_OUTPUT_PATH"header", "we");
87    PLUGIN_CHECK(file != NULL, return, "Failed to open file "BOOTCHART_OUTPUT_PATH"header");
88
89    (void)fprintf(file, "version = openharmony init\n");
90    (void)fprintf(file, "title = Boot chart for openharmony (%s)\n", date);
91    (void)fprintf(file, "system.uname = %s %s %s %s\n", uts.sysname, uts.release, uts.version, uts.machine);
92    if (strlen(release) > 0) {
93        (void)fprintf(file, "system.release = %s\n", release);
94    }
95    (void)fprintf(file, "system.cpu = %s\n", uts.machine);
96    (void)fprintf(file, "system.kernel.options = %s\n", cmdLine);
97    (void)fclose(file);
98}
99
100BOOTCHART_STATIC void BootchartLogFile(FILE *log, const char *procfile)
101{
102    (void)fprintf(log, "%lld\n", GetJiffies());
103    char *data = ReadFileToBuffer(procfile, g_bootchartCtrl->buffer, g_bootchartCtrl->bufferSize);
104    if (data != NULL) {
105        (void)fprintf(log, "%s\n", data);
106    }
107}
108
109BOOTCHART_STATIC void BootchartLogProcessStat(FILE *log, pid_t pid)
110{
111    static char path[255] = { }; // 255 path length
112    static char nameBuffer[255] = { }; // 255 path length
113    int ret = sprintf_s(path, sizeof(path) - 1, "/proc/%d/cmdline", pid);
114    PLUGIN_CHECK(ret > 0, return, "Failed to format path %d", pid);
115    path[ret] = '\0';
116
117    char *name = ReadFileToBuffer(path, nameBuffer, sizeof(nameBuffer));
118    // Read process stat line
119    ret = sprintf_s(path, sizeof(path) - 1, "/proc/%d/stat", pid);
120    PLUGIN_CHECK(ret > 0, return, "Failed to format path %d", pid);
121    path[ret] = '\0';
122
123    char *stat = ReadFileToBuffer(path, g_bootchartCtrl->buffer, g_bootchartCtrl->bufferSize);
124    if (stat == NULL) {
125        return;
126    }
127    if (name != NULL && strlen(name) > 0) {
128        char *end = NULL;
129        char *start = strstr(stat, "(");
130        if (start != NULL) {
131            end = strstr(start, ")");
132        }
133        if (end != NULL) {
134            stat[start - stat + 1] = '\0';
135            (void)fputs(stat, log);
136            (void)fputs(name, log);
137            (void)fputs(end, log);
138        } else {
139            (void)fputs(stat, log);
140        }
141    } else {
142        (void)fputs(stat, log);
143    }
144}
145
146BOOTCHART_STATIC void bootchartLogProcess(FILE *log)
147{
148    (void)fprintf(log, "%lld\n", GetJiffies());
149    DIR *pDir = opendir("/proc");
150    PLUGIN_CHECK(pDir != NULL, return, "Read dir /proc failed.%d", errno);
151    struct dirent *entry;
152    while ((entry = readdir(pDir)) != NULL) {
153        pid_t pid = (pid_t)atoi(entry->d_name); // Only process processor
154        if (pid == 0) {
155            continue;
156        }
157        BootchartLogProcessStat(log, pid);
158    }
159    closedir(pDir);
160    (void)fputc('\n', log);
161}
162
163BOOTCHART_STATIC void *BootchartThreadMain(void *data)
164{
165    PLUGIN_LOGI("bootcharting start");
166    FILE *statFile = fopen(BOOTCHART_OUTPUT_PATH"proc_stat.log", "w");
167    FILE *procFile = fopen(BOOTCHART_OUTPUT_PATH"proc_ps.log", "w");
168    FILE *diskFile = fopen(BOOTCHART_OUTPUT_PATH"proc_diskstats.log", "w");
169    do {
170        if (statFile == NULL || procFile == NULL || diskFile == NULL) {
171            PLUGIN_LOGE("Failed to open file");
172            break;
173        }
174        BootchartLogHeader();
175        while (1) {
176            pthread_mutex_lock(&(g_bootchartCtrl->mutex));
177            struct timespec abstime = {0};
178            struct timeval now = {0};
179            const long timeout = 200; // wait time 200ms
180            gettimeofday(&now, NULL);
181            long nsec = now.tv_usec * 1000 + (timeout % 1000) * 1000000; // 1000 unit 1000000 unit nsec
182            abstime.tv_sec = now.tv_sec + nsec / 1000000000 + timeout / 1000; // 1000 unit 1000000000 unit nsec
183            abstime.tv_nsec = nsec % 1000000000; // 1000000000 unit nsec
184            pthread_cond_timedwait(&(g_bootchartCtrl->cond), &(g_bootchartCtrl->mutex), &abstime);
185            if (g_bootchartCtrl->stop) {
186                pthread_mutex_unlock(&(g_bootchartCtrl->mutex));
187                break;
188            }
189            pthread_mutex_unlock(&(g_bootchartCtrl->mutex));
190            PLUGIN_LOGV("bootcharting running");
191            BootchartLogFile(statFile, "/proc/stat");
192            BootchartLogFile(diskFile, "/proc/diskstats");
193            bootchartLogProcess(procFile);
194        }
195    } while (0);
196
197    if (statFile != NULL) {
198        (void)fflush(statFile);
199        (void)fclose(statFile);
200    }
201    if (procFile != NULL) {
202        (void)fflush(procFile);
203        (void)fclose(procFile);
204    }
205    if (diskFile != NULL) {
206        (void)fflush(diskFile);
207        (void)fclose(diskFile);
208    }
209    PLUGIN_LOGI("bootcharting stop");
210    return NULL;
211}
212
213BOOTCHART_STATIC void BootchartDestory(void)
214{
215    pthread_mutex_destroy(&(g_bootchartCtrl->mutex));
216    pthread_cond_destroy(&(g_bootchartCtrl->cond));
217    free(g_bootchartCtrl);
218    g_bootchartCtrl = NULL;
219}
220
221BOOTCHART_STATIC int DoBootchartStart(void)
222{
223    if (g_bootchartCtrl != NULL) {
224        PLUGIN_LOGI("bootcharting has been start");
225        return 0;
226    }
227    g_bootchartCtrl = malloc(sizeof(BootchartCtrl));
228    PLUGIN_CHECK(g_bootchartCtrl != NULL, return -1, "Failed to alloc mem for bootchart");
229    g_bootchartCtrl->bufferSize = DEFAULT_BUFFER;
230
231    int ret = pthread_mutex_init(&(g_bootchartCtrl->mutex), NULL);
232    PLUGIN_CHECK(ret == 0, BootchartDestory();
233        return -1, "Failed to init mutex");
234    ret = pthread_cond_init(&(g_bootchartCtrl->cond), NULL);
235    PLUGIN_CHECK(ret == 0, BootchartDestory();
236        return -1, "Failed to init cond");
237
238    g_bootchartCtrl->stop = 0;
239    ret = pthread_create(&(g_bootchartCtrl->threadId), NULL, BootchartThreadMain, (void *)g_bootchartCtrl);
240    PLUGIN_CHECK(ret == 0, BootchartDestory();
241        return -1, "Failed to init cond");
242
243    pthread_mutex_lock(&(g_bootchartCtrl->mutex));
244    pthread_cond_signal(&(g_bootchartCtrl->cond));
245    pthread_mutex_unlock(&(g_bootchartCtrl->mutex));
246    g_bootchartCtrl->start = 1;
247    return 0;
248}
249
250BOOTCHART_STATIC int DoBootchartStop(void)
251{
252    if (g_bootchartCtrl == NULL || !g_bootchartCtrl->start) {
253        PLUGIN_LOGI("bootcharting not start");
254        return 0;
255    }
256    pthread_mutex_lock(&(g_bootchartCtrl->mutex));
257    g_bootchartCtrl->stop = 1;
258    pthread_cond_signal(&(g_bootchartCtrl->cond));
259    pthread_mutex_unlock(&(g_bootchartCtrl->mutex));
260    pthread_join(g_bootchartCtrl->threadId, NULL);
261    BootchartDestory();
262    PLUGIN_LOGI("bootcharting stopped");
263    return 0;
264}
265
266BOOTCHART_STATIC int DoBootchartCmd(int id, const char *name, int argc, const char **argv)
267{
268    PLUGIN_LOGI("DoBootchartCmd argc %d %s", argc, name);
269    PLUGIN_CHECK(argc >= 1, return -1, "Invalid parameter");
270    if (strcmp(argv[0], "start") == 0) {
271        return DoBootchartStart();
272    } else if (strcmp(argv[0], "stop") == 0) {
273        return DoBootchartStop();
274    }
275    return 0;
276}
277
278static int32_t g_executorId = -1;
279BOOTCHART_STATIC int BootchartInit(void)
280{
281    if (g_executorId == -1) {
282        g_executorId = AddCmdExecutor("bootchart", DoBootchartCmd);
283        PLUGIN_LOGI("BootchartInit executorId %d", g_executorId);
284    }
285    return 0;
286}
287
288BOOTCHART_STATIC void BootchartExit(void)
289{
290    PLUGIN_LOGI("BootchartExit executorId %d", g_executorId);
291    if (g_executorId != -1) {
292        RemoveCmdExecutor("bootchart", g_executorId);
293    }
294}
295
296MODULE_CONSTRUCTOR(void)
297{
298    PLUGIN_LOGI("DoBootchartStart now ...");
299    BootchartInit();
300}
301
302MODULE_DESTRUCTOR(void)
303{
304    PLUGIN_LOGI("DoBootchartStop now ...");
305    DoBootchartStop();
306    BootchartExit();
307}
308