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