* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cstdlib>
#include <climits>
#include <unordered_map>
#include <functional>
#include <mutex>
#include "app_event.h"
#include "app_event_processor_mgr.h"
#include "hilog_wrapper.h"
#include "api_event_reporter.h"
#include "uuid.h"
#include <sys/stat.h>
namespace OHOS {
namespace Accessibility {
std::string ApiEventReporter::g_fileContent = "";
int64_t ApiEventReporter::g_processorId = -1;
std::mutex ApiEventReporter::g_apiOperationMutex;
std::mutex ApiEventReporter::g_apiExpandableDataMutex;
constexpr size_t UUID_CHAR_ARRAY_LENGTH = 37;
ApiEventReporter::ApiEventReporter()
{
}
ApiEventReporter::~ApiEventReporter()
{
m_thresholdData.clear();
}
bool ApiEventReporter::IsAppEventProcessorValid()
{
if (g_processorId <= NULLPTR_PROCCESSORID) {
g_processorId = AddProcessor();
}
if (g_processorId <= NULLPTR_PROCCESSORID) {
return false;
}
return true;
}
bool ApiEventReporter::LoadConfigurationFile(const std::string &configFile)
{
if (!IsValidPath(configFile)) {
return false;
}
std::ifstream file(configFile);
if (!file.is_open()) {
HILOG_ERROR("Unable to open api operation config file!");
return false;
}
std::ostringstream oss;
std::string line;
while (std::getline(file, line)) {
line = Trim(line);
if (!line.empty() && line[0] != '#') {
oss << line << "\n";
}
}
g_fileContent = oss.str();
file.close();
return true;
}
void ApiEventReporter::GetConfigurationParams(ApiReportConfig &reportConfig, ApiEventConfig &eventConfig)
{
std::istringstream stream(g_fileContent);
ParseApiOperationManagement(stream, reportConfig, eventConfig);
}
std::string ApiEventReporter::Trim(const std::string &str)
{
const char* whitespace = " \t\n\r";
size_t first = str.find_first_not_of(whitespace);
size_t last = str.find_last_not_of(whitespace);
return (first == std::string::npos) ? "" : str.substr(first, last - first + 1);
}
std::pair<std::string, std::string> ApiEventReporter::ParseKeyValue(const std::string &line)
{
size_t colonPos = line.find(':');
if (colonPos != std::string::npos) {
std::string key = Trim(line.substr(0, colonPos));
std::string value = Trim(line.substr(colonPos + 1));
key.erase(std::remove(key.begin(), key.end(), '"'), key.end());
value.erase(std::remove(value.begin(), value.end(), '"'), value.end());
if (!value.empty() && value.back() == ',') {
value.pop_back();
}
return std::make_pair(key, value);
}
return std::make_pair("", "");
}
bool ApiEventReporter::TryParseInt(const std::string &str, int &out)
{
char* end;
long val = strtol(str.c_str(), &end, 10);
if (*end == '\0' && end != str.c_str() && val >= INT_MIN && val <= INT_MAX) {
out = static_cast<int>(val);
return true;
} else {
HILOG_ERROR("Invalid integer: %{public}s", str.c_str());
return false;
}
}
void ApiEventReporter::ParseReportConfig(std::istringstream &stream, ApiReportConfig &reportConfig)
{
std::unordered_map<std::string, std::function<void(const std::string&)>> configMap = {
{"config_name", [&](const std::string &value) { reportConfig.config_name = value; }},
{"config_appId", [&](const std::string &value) { reportConfig.config_appId = value; }},
{"config_routeInfo", [&](const std::string &value) { reportConfig.config_routeInfo = value; }},
{"config_TriggerCond.timeout", [&](const std::string &value) {
int temp;
if (TryParseInt(value, temp)) {
reportConfig.config_timeout = temp;
} else {
HILOG_ERROR("Invalid integer for config_timeout: %{public}s", value.c_str());
}
}},
{"config_TriggerCond.row", [&](const std::string &value) {
int temp;
if (TryParseInt(value, temp)) {
reportConfig.config_row = temp;
} else {
HILOG_ERROR("Invalid integer for config_row: %{public}s", value.c_str());
}
}}
};
std::string line;
while (std::getline(stream, line)) {
line = Trim(line);
if (line == "},") {
break;
}
auto keyValue = ParseKeyValue(line);
auto it = configMap.find(keyValue.first);
if (it != configMap.end()) {
it->second(keyValue.second);
}
}
}
void ApiEventReporter::ParseEvent(std::istringstream &stream, ApiEvent &event)
{
std::unordered_map<std::string, std::function<void(const std::string&)>> eventMap = {
{"domain", [&](const std::string &value) { event.domain = value; }},
{"name", [&](const std::string &value) { event.name = value; }},
{"isRealTime", [&](const std::string &value) { event.isRealTime = (value == "true"); }}
};
std::string line;
while (std::getline(stream, line)) {
line = Trim(line);
if (line == "},") {
break;
}
auto keyValue = ParseKeyValue(line);
auto it = eventMap.find(keyValue.first);
if (it != eventMap.end()) {
it->second(keyValue.second);
}
}
}
void ApiEventReporter::ParseEventConfig(std::istringstream &stream, ApiEventConfig &eventConfig)
{
std::unordered_map<std::string, std::function<void(std::istringstream&)>> eventConfigMap = {
{"\"event1\": {", [&](std::istringstream &stream) { ParseEvent(stream, eventConfig.event1); }},
{"\"event2\": {", [&](std::istringstream &stream) { ParseEvent(stream, eventConfig.event2); }},
{"\"event3\": {", [&](std::istringstream &stream) { ParseEvent(stream, eventConfig.event3); }}
};
std::string line;
while (std::getline(stream, line)) {
line = Trim(line);
if (line == "},") {
break;
}
auto it = eventConfigMap.find(line);
if (it != eventConfigMap.end()) {
it->second(stream);
}
}
}
void ApiEventReporter::ParseApiOperationManagement(std::istringstream &stream, ApiReportConfig &reportConfig,
ApiEventConfig &eventConfig)
{
std::unordered_map<std::string, std::function<void(std::istringstream&)>> apiOpMgmtMap = {
{"\"report_config\": {", [&](std::istringstream &stream) { ParseReportConfig(stream, reportConfig); }},
{"\"event_config\": {", [&](std::istringstream &stream) { ParseEventConfig(stream, eventConfig); }}
};
std::string line;
while (std::getline(stream, line)) {
line = Trim(line);
if (line == "}") {
break;
}
auto it = apiOpMgmtMap.find(line);
if (it != apiOpMgmtMap.end()) {
it->second(stream);
}
}
}
int64_t ApiEventReporter::AddProcessor()
{
HILOG_INFO("AddProcessor enter.");
std::lock_guard<std::mutex> lock(g_apiOperationMutex);
ApiReportConfig reportConfig;
ApiEventConfig eventConfig;
if (g_fileContent.empty()) {
if (LoadConfigurationFile(ACCESSIBILITY_API_OPERATION_CONFIG_PATH) != true) {
HILOG_ERROR("AddProcessor LoadConfigurationFile error!");
return -1;
}
}
GetConfigurationParams(reportConfig, eventConfig);
HiviewDFX::HiAppEvent::ReportConfig config;
config.name = reportConfig.config_name;
config.appId = reportConfig.config_appId;
config.routeInfo = reportConfig.config_routeInfo;
config.triggerCond.timeout = reportConfig.config_timeout;
config.triggerCond.row = reportConfig.config_row;
config.eventConfigs.clear();
HiviewDFX::HiAppEvent::EventConfig event1;
event1.domain = eventConfig.event1.domain;
event1.name = eventConfig.event1.name;
event1.isRealTime = eventConfig.event1.isRealTime;
config.eventConfigs.push_back(event1);
HiviewDFX::HiAppEvent::EventConfig event2;
event2.domain = eventConfig.event2.domain;
event2.name = eventConfig.event2.name;
event2.isRealTime = eventConfig.event2.isRealTime;
config.eventConfigs.push_back(event2);
HiviewDFX::HiAppEvent::EventConfig event3;
event3.domain = eventConfig.event3.domain;
event3.name = eventConfig.event3.name;
event3.isRealTime = eventConfig.event3.isRealTime;
config.eventConfigs.push_back(event3);
g_processorId = HiviewDFX::HiAppEvent::AppEventProcessorMgr::AddProcessor(config);
return g_processorId;
}
std::string RandomUuid()
{
uuid_t uuid;
uuid_generate_random(uuid);
char uuidChars[UUID_CHAR_ARRAY_LENGTH] = {'\0'};
uuid_unparse(uuid, uuidChars);
return std::string(uuidChars);
}
int64_t ApiEventReporter::GetCurrentTime()
{
int64_t time = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
return time;
}
void ApiEventReporter::ThresholdWriteEndEvent(int result, std::string apiName, int64_t beginTime,
int32_t thresholdValue)
{
std::lock_guard<std::mutex> lock(g_apiExpandableDataMutex);
auto expandableData = CacheEventInfo(apiName, beginTime, result);
if (expandableData == nullptr) {
return;
}
if (thresholdValue <= 0) {
uint32_t dataCount = expandableData->runTime.size();
return;
}
int32_t dataCount = static_cast<int32_t>(expandableData->runTime.size());
HILOG_DEBUG("ThresholdWriteEndEvent apiName: %{public}s, dataCount: %{public}d, thresholdValue: %{public}d",
apiName.c_str(), dataCount, thresholdValue);
if (dataCount % static_cast<uint32_t>(thresholdValue) != 0) {
return;
}
ExecuteThresholdWriteEndEvent(apiName, expandableData, static_cast<int32_t>(dataCount));
}
std::shared_ptr<EventPeriodExpandableData> ApiEventReporter::CacheEventInfo(std::string apiName,
int64_t beginTime, int result)
{
std::lock_guard<std::mutex> lock(g_apiOperationMutex);
if (m_thresholdData.find(apiName) == m_thresholdData.end()) {
std::shared_ptr<EventPeriodExpandableData> expandableData = std::make_shared<EventPeriodExpandableData>();
if (expandableData == nullptr) {
HILOG_ERROR("ApiEventReporter CacheEventInfo expandableData make_shared failed");
return nullptr;
}
m_thresholdData.insert(std::pair<std::string, std::shared_ptr<EventPeriodExpandableData>>(apiName,
expandableData));
}
auto expandableData = m_thresholdData[apiName];
int64_t costTime = GetCurrentTime() - beginTime;
expandableData->runTime.push_back(costTime);
expandableData->sumTime += costTime;
if (result == 0) {
expandableData->successCount++;
}
return expandableData;
}
void ApiEventReporter::ExecuteThresholdWriteEndEvent(std::string apiName,
std::shared_ptr<EventPeriodExpandableData> expandableData, int32_t dataCount)
{
HILOG_DEBUG("ExecuteThresholdWriteEndEvent enter.");
HiviewDFX::HiAppEvent::Event event("api_diagnostic", "api_called_stat_cnt", OHOS::HiviewDFX::HiAppEvent::BEHAVIOR);
event.AddParam("trans_id", std::string(""));
event.AddParam("api_name", apiName);
event.AddParam("sdk_name", std::string("AccessibilityKit"));
event.AddParam("call_times", dataCount);
event.AddParam("call_times", static_cast<int32_t>(expandableData->runTime.size()));
int64_t maxElement = 0;
int64_t minElement = 0;
if (!expandableData->runTime.empty()) {
maxElement = *max_element(expandableData->runTime.begin(), expandableData->runTime.end());
minElement = *min_element(expandableData->runTime.begin(), expandableData->runTime.end());
}
event.AddParam("max_cost_time", maxElement);
event.AddParam("min_cost_time", minElement);
event.AddParam("total_cost_time", expandableData->sumTime);
if (!IsAppEventProcessorValid()) {
HILOG_ERROR("ExecuteThresholdWriteEndEvent processorid invalid!");
return;
}
HiviewDFX::HiAppEvent::Write(event);
expandableData->runTime.clear();
expandableData->successCount = 0;
expandableData->sumTime = 0;
}
bool ApiEventReporter::IsValidPath(const std::string& filePath)
{
std::string realFilePath;
if (!IsReal(filePath, realFilePath)) {
return false;
}
struct stat st;
if (stat(realFilePath.c_str(), &st)) {
return false;
}
return true;
}
bool ApiEventReporter::IsReal(const std::string& file, std::string& realFile)
{
char realPath[PATH_MAX];
if (realpath(file.c_str(), realPath) == nullptr) {
return false;
}
realFile = std::string(realPath);
return true;
}
}
}