1 // Copyright 2021 Google LLC
2 //
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 #include "util.hpp"
16 
17 #include <unistd.h>
18 
19 #include <phosphor-logging/log.hpp>
20 
21 #include <cmath>
22 #include <cstdlib>
23 #include <fstream>
24 #include <sstream>
25 #include <string>
26 #include <string_view>
27 
28 namespace metric_blob
29 {
30 
31 using phosphor::logging::log;
32 using level = phosphor::logging::level;
33 
34 char controlCharsToSpace(char c)
35 {
36     if (c < 32)
37     {
38         c = ' ';
39     }
40     return c;
41 }
42 
43 long getTicksPerSec()
44 {
45     return sysconf(_SC_CLK_TCK);
46 }
47 
48 std::string readFileIntoString(const std::string_view fileName)
49 {
50     std::stringstream ss;
51     std::ifstream ifs(fileName.data());
52     while (ifs.good())
53     {
54         std::string line;
55         std::getline(ifs, line);
56         ss << line;
57         if (ifs.good())
58             ss << std::endl;
59     }
60     return ss.str();
61 }
62 
63 bool isNumericPath(const std::string_view path, int& value)
64 {
65     size_t p = path.rfind('/');
66     if (p == std::string::npos)
67     {
68         return false;
69     }
70     int id = 0;
71     for (size_t i = p + 1; i < path.size(); ++i)
72     {
73         const char ch = path[i];
74         if (ch < '0' || ch > '9')
75             return false;
76         else
77         {
78             id = id * 10 + (ch - '0');
79         }
80     }
81     value = id;
82     return true;
83 }
84 
85 // Trims all control characters at the end of a string.
86 std::string trimStringRight(std::string_view s)
87 {
88     std::string ret(s.data());
89     while (!ret.empty())
90     {
91         if (ret.back() <= 32)
92             ret.pop_back();
93         else
94             break;
95     }
96     return ret;
97 }
98 
99 std::string getCmdLine(const int pid)
100 {
101     const std::string& cmdlinePath =
102         "/proc/" + std::to_string(pid) + "/cmdline";
103 
104     std::string cmdline = readFileIntoString(cmdlinePath);
105     for (size_t i = 0; i < cmdline.size(); ++i)
106     {
107         cmdline[i] = controlCharsToSpace(cmdline[i]);
108     }
109 
110     // Trim empty strings
111     cmdline = trimStringRight(cmdline);
112 
113     return cmdline;
114 }
115 
116 // strtok is used in this function in order to avoid usage of <sstream>.
117 // However, that would require us to create a temporary std::string.
118 TcommUtimeStime parseTcommUtimeStimeString(std::string_view content,
119                                            const long ticksPerSec)
120 {
121     TcommUtimeStime ret;
122     ret.tcomm = "";
123     ret.utime = ret.stime = 0;
124 
125     const float invTicksPerSec = 1.0f / static_cast<float>(ticksPerSec);
126 
127     // pCol now points to the first part in content after content is split by
128     // space.
129     // This is not ideal,
130     std::string temp(content);
131     char* pCol = strtok(temp.data(), " ");
132 
133     if (pCol != nullptr)
134     {
135         const int fields[] = {1, 13, 14}; // tcomm, utime, stime
136         int fieldIdx = 0;
137         for (int colIdx = 0; colIdx < 15; ++colIdx)
138         {
139             if (fieldIdx < 3 && colIdx == fields[fieldIdx])
140             {
141                 switch (fieldIdx)
142                 {
143                     case 0:
144                     {
145                         ret.tcomm = std::string(pCol);
146                         break;
147                     }
148                     case 1:
149                         [[fallthrough]];
150                     case 2:
151                     {
152                         int ticks = std::atoi(pCol);
153                         float t = static_cast<float>(ticks) * invTicksPerSec;
154 
155                         if (fieldIdx == 1)
156                         {
157                             ret.utime = t;
158                         }
159                         else if (fieldIdx == 2)
160                         {
161                             ret.stime = t;
162                         }
163                         break;
164                     }
165                 }
166                 ++fieldIdx;
167             }
168             pCol = strtok(nullptr, " ");
169         }
170     }
171 
172     if (ticksPerSec <= 0)
173     {
174         log<level::ERR>("ticksPerSec is equal or less than zero");
175     }
176 
177     return ret;
178 }
179 
180 TcommUtimeStime getTcommUtimeStime(const int pid, const long ticksPerSec)
181 {
182     const std::string& statPath = "/proc/" + std::to_string(pid) + "/stat";
183     return parseTcommUtimeStimeString(readFileIntoString(statPath),
184                                       ticksPerSec);
185 }
186 
187 // Returns true if successfully parsed and false otherwise. If parsing was
188 // successful, value is set accordingly.
189 // Input: "MemAvailable:      1234 kB"
190 // Returns true, value set to 1234
191 bool parseMeminfoValue(const std::string_view content,
192                        const std::string_view keyword, int& value)
193 {
194     size_t p = content.find(keyword);
195     if (p != std::string::npos)
196     {
197         std::string_view v = content.substr(p + keyword.size());
198         p = v.find("kB");
199         if (p != std::string::npos)
200         {
201             v = v.substr(0, p);
202             value = std::atoi(v.data());
203             return true;
204         }
205     }
206     return false;
207 }
208 
209 bool parseProcUptime(const std::string_view content, double& uptime,
210                      double& idleProcessTime)
211 {
212     double t0, t1; // Attempts to parse uptime and idleProcessTime
213     int ret = sscanf(content.data(), "%lf %lf", &t0, &t1);
214     if (ret == 2 && std::isfinite(t0) && std::isfinite(t1))
215     {
216         uptime = t0;
217         idleProcessTime = t1;
218         return true;
219     }
220     return false;
221 }
222 
223 } // namespace metric_blob