1/*
2 * Copyright (c) Huawei Technologies Co., Ltd. 2021. All rights reserved.
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 "diskio_data_plugin.h"
17
18#include <ctime>
19
20#include "buffer_splitter.h"
21#include "diskio_plugin_result.pbencoder.h"
22
23namespace {
24using namespace OHOS::Developtools::Profiler;
25constexpr size_t READ_BUFFER_SIZE = 1024 * 16;
26} // namespace
27
28DiskioDataPlugin::DiskioDataPlugin()
29{
30    ioEntry_ = nullptr;
31    buffer_ = nullptr;
32    path_ = "/proc/vmstat";
33    err_ = -1;
34    prevRdSectorsKb_ = 0;
35    prevWrSectorsKb_ = 0;
36    prevTimestamp_.tv_sec = 0;
37    prevTimestamp_.tv_nsec = 0;
38}
39
40DiskioDataPlugin::~DiskioDataPlugin()
41{
42    PROFILER_LOG_INFO(LOG_CORE, "%s:~DiskioDataPlugin!", __func__);
43    if (buffer_ != nullptr) {
44        free(buffer_);
45        buffer_ = nullptr;
46    }
47}
48
49int DiskioDataPlugin::Start(const uint8_t* configData, uint32_t configSize)
50{
51    buffer_ = malloc(READ_BUFFER_SIZE);
52    CHECK_NOTNULL(buffer_, RET_FAIL, "%s:malloc buffer_ failed!", __func__);
53
54    CHECK_TRUE(protoConfig_.ParseFromArray(configData, configSize) > 0, RET_FAIL,
55               "%s:parseFromArray failed!", __func__);
56
57    if (protoConfig_.report_io_stats()) {
58        ioEntry_ = std::make_shared<IoStats>(protoConfig_.report_io_stats());
59    }
60    PROFILER_LOG_INFO(LOG_CORE, "%s:start success!", __func__);
61    return RET_SUCC;
62}
63
64int DiskioDataPlugin::ReportOptimize(RandomWriteCtx* randomWrite)
65{
66    ProtoEncoder::DiskioData dataProto(randomWrite);
67    WriteDiskioData(dataProto);
68
69    if (protoConfig_.report_io_stats() && ioEntry_ != nullptr) {
70        ioEntry_->GetIoData();
71        auto* statsData = dataProto.mutable_statsdata();
72        ioEntry_->PutPluginStatsData(*statsData);
73    }
74
75    int32_t msgSize = dataProto.Finish();
76    return static_cast<int>(msgSize);
77}
78
79int DiskioDataPlugin::Report(uint8_t* data, uint32_t dataSize)
80{
81    DiskioData dataProto;
82    uint32_t length;
83
84    WriteDiskioData(dataProto);
85
86    if (protoConfig_.report_io_stats() && ioEntry_ != nullptr) {
87        ioEntry_->GetIoData();
88        auto* statsData = dataProto.mutable_statsdata();
89        ioEntry_->PutPluginStatsData(*statsData);
90    }
91
92    length = dataProto.ByteSizeLong();
93    if (length > dataSize) {
94        return -length;
95    }
96    if (dataProto.SerializeToArray(data, length) > 0) {
97        return length;
98    }
99    return 0;
100}
101
102int DiskioDataPlugin::Stop()
103{
104    if (buffer_ != nullptr) {
105        free(buffer_);
106        buffer_ = nullptr;
107    }
108    if (ioEntry_ != nullptr) {
109        ioEntry_.reset();
110        ioEntry_ = nullptr;
111    }
112    PROFILER_LOG_INFO(LOG_CORE, "%s:plugin:stop success!", __func__);
113    return 0;
114}
115
116int32_t DiskioDataPlugin::ReadFile(std::string& fileName)
117{
118    int fd = -1;
119    ssize_t bytesRead = 0;
120    char realPath[PATH_MAX + 1] = {0};
121    CHECK_TRUE((fileName.length() < PATH_MAX) && (realpath(fileName.c_str(), realPath) != nullptr), RET_FAIL,
122               "%s:path is invalid: %s, errno=%d", __func__, fileName.c_str(), errno);
123    fd = open(realPath, O_RDONLY | O_CLOEXEC);
124    if (fd == -1) {
125        PROFILER_LOG_ERROR(LOG_CORE, "%s:failed to open(%s), errno=%d", __func__, fileName.c_str(), errno);
126        err_ = errno;
127        return RET_FAIL;
128    }
129    if (buffer_ == nullptr) {
130        PROFILER_LOG_ERROR(LOG_CORE, "%s:empty address, buffer_ is NULL", __func__);
131        err_ = RET_NULL_ADDR;
132        close(fd);
133        return RET_FAIL;
134    }
135    bytesRead = read(fd, buffer_, READ_BUFFER_SIZE - 1);
136    if (bytesRead <= 0) {
137        close(fd);
138        PROFILER_LOG_ERROR(LOG_CORE, "%s:failed to read(%s), errno=%d", __func__, fileName.c_str(), errno);
139        err_ = errno;
140        return RET_FAIL;
141    }
142    close(fd);
143
144    return bytesRead;
145}
146
147template <typename T> void DiskioDataPlugin::SetTimestamp(T& diskioData)
148{
149    auto* prevTimestamp = diskioData.mutable_prev_timestamp();
150    prevTimestamp->set_tv_sec(prevTimestamp_.tv_sec);
151    prevTimestamp->set_tv_nsec(prevTimestamp_.tv_nsec);
152
153    timespec time;
154    clock_gettime(CLOCK_MONOTONIC, &time);
155    auto* timestamp = diskioData.mutable_timestamp();
156    timestamp->set_tv_sec(time.tv_sec);
157    timestamp->set_tv_nsec(time.tv_nsec);
158
159    prevTimestamp_.tv_sec = time.tv_sec;
160    prevTimestamp_.tv_nsec = time.tv_nsec;
161}
162
163template <typename T> void DiskioDataPlugin::SetDiskioData(T& diskioData, const char* pFile, uint32_t fileLen)
164{
165    BufferSplitter totalbuffer(const_cast<char*>(pFile), fileLen + 1);
166    int64_t rd_sectors_kb = 0;
167    int64_t wr_sectors_kb = 0;
168
169    do {
170        totalbuffer.NextWord(' ');
171        std::string curWord = std::string(totalbuffer.CurWord(), totalbuffer.CurWordSize());
172        if (strcmp(curWord.c_str(), "pgpgin") == 0) {
173            if (!totalbuffer.NextWord('\n')) {
174                PROFILER_LOG_ERROR(LOG_CORE, "%s:failed to get pgpgin, CurWord() = %s",
175                                   __func__, totalbuffer.CurWord());
176                break;
177            }
178            curWord = std::string(totalbuffer.CurWord(), totalbuffer.CurWordSize());
179            rd_sectors_kb = atoi(curWord.c_str());
180        } else if (strcmp(curWord.c_str(), "pgpgout") == 0) {
181            if (!totalbuffer.NextWord('\n')) {
182                PROFILER_LOG_ERROR(LOG_CORE, "%s:failed to get pgpgout, CurWord() = %s",
183                                   __func__, totalbuffer.CurWord());
184                break;
185            }
186            curWord = std::string(totalbuffer.CurWord(), totalbuffer.CurWordSize());
187            wr_sectors_kb = atoi(curWord.c_str());
188        }
189    } while (totalbuffer.NextLine());
190
191    // 前一次系统从磁盘调入的总KB数rd1,通过时间间隔t内KB增量计算磁盘读取速率(rd2-rd1)/t
192    diskioData.set_prev_rd_sectors_kb(prevRdSectorsKb_);
193    // 前一次系统调出到磁盘的总KB数wr1,通过时间间隔t内KB增量计算磁盘写入速率(wr2-wr1)/t
194    diskioData.set_prev_wr_sectors_kb(prevWrSectorsKb_);
195    diskioData.set_rd_sectors_kb(rd_sectors_kb); // 当前系统从磁盘调入的总KB数rd2
196    diskioData.set_wr_sectors_kb(wr_sectors_kb); // 当前系统调出到磁盘的总KB数wr2
197    prevRdSectorsKb_ = rd_sectors_kb;
198    prevWrSectorsKb_ = wr_sectors_kb;
199    SetTimestamp(diskioData); // 设置前一次时间戳和当前时间戳,以便计算两次获取数据的时间间隔t
200}
201
202template <typename T> void DiskioDataPlugin::WriteDiskioData(T& diskioData)
203{
204    int32_t ret = ReadFile(path_);
205    if (ret == RET_FAIL) {
206        return;
207    }
208    if ((buffer_ == nullptr) || (ret == 0)) {
209        return;
210    }
211
212    SetDiskioData(diskioData, reinterpret_cast<char*>(buffer_), ret);
213}