1 /*
2 * Copyright (C) 2023, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "catalog.h"
18
19 #include <expresscatalog-utils.h>
20 #include <google/protobuf/text_format.h>
21 #include <inttypes.h>
22 #include <utils/hash/farmhash.h>
23
24 #include <filesystem>
25 #include <fstream>
26 #include <iostream>
27 #include <regex>
28 #include <sstream>
29 #include <unordered_set>
30
31 #define DEBUG true
32
33 using std::map;
34 using std::string;
35
36 namespace fs = std::filesystem;
37 namespace pb = google::protobuf;
38
39 namespace android {
40 namespace express {
41
42 namespace {
43
validateMetricId(const string & metricId)44 bool validateMetricId(const string& metricId) {
45 // validation is done according to regEx
46 static const char* regExStr = "[a-z]+[a-z_0-9]*[.]value_[a-z]+[a-z_0-9]*";
47 static const std::regex expr(regExStr);
48
49 if (!std::regex_match(metricId, expr)) {
50 LOGE("Metric Id \"%s\"does not follow naming convention which is \"%s\"\n",
51 metricId.c_str(), regExStr);
52 return false;
53 }
54
55 return true;
56 }
57
readMetrics(const fs::path & cfgFile,map<string,ExpressMetric> & metrics)58 bool readMetrics(const fs::path& cfgFile, map<string, ExpressMetric>& metrics) {
59 std::ifstream fileStream(cfgFile.c_str());
60 std::stringstream buffer;
61 buffer << fileStream.rdbuf();
62
63 LOGD("Metrics config content:\n %s\n", buffer.str().c_str());
64
65 ExpressMetricConfigFile cfMessage;
66 if (!pb::TextFormat::ParseFromString(buffer.str(), &cfMessage)) {
67 LOGE("Can not process config file %s\n", cfgFile.c_str());
68 return false;
69 }
70
71 LOGD("Metrics amount in the file %d\n", cfMessage.express_metric_size());
72
73 for (int i = 0; i < cfMessage.express_metric_size(); i++) {
74 const ExpressMetric& metric = cfMessage.express_metric(i);
75
76 if (!metric.has_id()) {
77 LOGE("No id is defined for metric index %d. Skip\n", i);
78 return false;
79 }
80
81 LOGD("Metric: %s\n", metric.id().c_str());
82
83 if (!validateMetricId(metric.id())) {
84 return false;
85 }
86
87 if (metrics.find(metric.id()) != metrics.end()) {
88 LOGE("Metric id redefinition error, broken uniqueness rule. Skip\n");
89 return false;
90 }
91
92 metrics[metric.id()] = metric;
93 }
94
95 return true;
96 }
97
98 } // namespace
99
readCatalog(const char * configDir,map<string,ExpressMetric> & metrics)100 bool readCatalog(const char* configDir, map<string, ExpressMetric>& metrics) {
101 MEASURE_FUNC();
102 auto configDirPath = configDir;
103
104 LOGD("Config dir %s\n", configDirPath.c_str());
105
106 for (const auto& entry : fs::directory_iterator(configDirPath)) {
107 LOGD("Checking %s\n", entry.path().c_str());
108 LOGD(" is_regular_file %d\n", fs::is_regular_file(entry.path()));
109 LOGD(" extension %s\n", entry.path().extension().c_str());
110
111 if (fs::is_regular_file(entry.path()) &&
112 strcmp(entry.path().extension().c_str(), ".cfg") == 0) {
113 LOGD("Located config: %s\n", entry.path().c_str());
114 if (!readMetrics(entry, metrics)) {
115 LOGE("Error parsing config: %s\n", entry.path().c_str());
116 return false;
117 }
118 }
119 }
120 LOGD("Catalog dir %s processed\n", configDirPath.c_str());
121
122 return true;
123 }
124
generateMetricsIds(const map<string,ExpressMetric> & metrics,MetricInfoMap & metricsIds)125 bool generateMetricsIds(const map<string, ExpressMetric>& metrics, MetricInfoMap& metricsIds) {
126 MEASURE_FUNC();
127 std::unordered_set<int64_t> currentHashes;
128
129 for (const auto& [metricId, expressMetric] : metrics) {
130 const int64_t hashId = farmhash::Fingerprint64(metricId.c_str(), metricId.size());
131
132 // check if there is a collision
133 if (currentHashes.find(hashId) != currentHashes.end()) {
134 LOGE("Detected hash name collision for a metric %s\n", metricId.c_str());
135 return false;
136 }
137 currentHashes.insert(hashId);
138 metricsIds[metricId] = {hashId, expressMetric.type()};
139 }
140
141 return true;
142 }
143
144 } // namespace express
145 } // namespace android
146