xref: /openbmc/linux/tools/perf/util/time-utils.c (revision ddc141e5)
1 // SPDX-License-Identifier: GPL-2.0
2 #include <stdlib.h>
3 #include <string.h>
4 #include <sys/time.h>
5 #include <linux/time64.h>
6 #include <time.h>
7 #include <errno.h>
8 #include <inttypes.h>
9 #include <math.h>
10 
11 #include "perf.h"
12 #include "debug.h"
13 #include "time-utils.h"
14 
15 int parse_nsec_time(const char *str, u64 *ptime)
16 {
17 	u64 time_sec, time_nsec;
18 	char *end;
19 
20 	time_sec = strtoul(str, &end, 10);
21 	if (*end != '.' && *end != '\0')
22 		return -1;
23 
24 	if (*end == '.') {
25 		int i;
26 		char nsec_buf[10];
27 
28 		if (strlen(++end) > 9)
29 			return -1;
30 
31 		strncpy(nsec_buf, end, 9);
32 		nsec_buf[9] = '\0';
33 
34 		/* make it nsec precision */
35 		for (i = strlen(nsec_buf); i < 9; i++)
36 			nsec_buf[i] = '0';
37 
38 		time_nsec = strtoul(nsec_buf, &end, 10);
39 		if (*end != '\0')
40 			return -1;
41 	} else
42 		time_nsec = 0;
43 
44 	*ptime = time_sec * NSEC_PER_SEC + time_nsec;
45 	return 0;
46 }
47 
48 static int parse_timestr_sec_nsec(struct perf_time_interval *ptime,
49 				  char *start_str, char *end_str)
50 {
51 	if (start_str && (*start_str != '\0') &&
52 	    (parse_nsec_time(start_str, &ptime->start) != 0)) {
53 		return -1;
54 	}
55 
56 	if (end_str && (*end_str != '\0') &&
57 	    (parse_nsec_time(end_str, &ptime->end) != 0)) {
58 		return -1;
59 	}
60 
61 	return 0;
62 }
63 
64 static int split_start_end(char **start, char **end, const char *ostr, char ch)
65 {
66 	char *start_str, *end_str;
67 	char *d, *str;
68 
69 	if (ostr == NULL || *ostr == '\0')
70 		return 0;
71 
72 	/* copy original string because we need to modify it */
73 	str = strdup(ostr);
74 	if (str == NULL)
75 		return -ENOMEM;
76 
77 	start_str = str;
78 	d = strchr(start_str, ch);
79 	if (d) {
80 		*d = '\0';
81 		++d;
82 	}
83 	end_str = d;
84 
85 	*start = start_str;
86 	*end = end_str;
87 
88 	return 0;
89 }
90 
91 int perf_time__parse_str(struct perf_time_interval *ptime, const char *ostr)
92 {
93 	char *start_str = NULL, *end_str;
94 	int rc;
95 
96 	rc = split_start_end(&start_str, &end_str, ostr, ',');
97 	if (rc || !start_str)
98 		return rc;
99 
100 	ptime->start = 0;
101 	ptime->end = 0;
102 
103 	rc = parse_timestr_sec_nsec(ptime, start_str, end_str);
104 
105 	free(start_str);
106 
107 	/* make sure end time is after start time if it was given */
108 	if (rc == 0 && ptime->end && ptime->end < ptime->start)
109 		return -EINVAL;
110 
111 	pr_debug("start time %" PRIu64 ", ", ptime->start);
112 	pr_debug("end time %" PRIu64 "\n", ptime->end);
113 
114 	return rc;
115 }
116 
117 static int parse_percent(double *pcnt, char *str)
118 {
119 	char *c, *endptr;
120 	double d;
121 
122 	c = strchr(str, '%');
123 	if (c)
124 		*c = '\0';
125 	else
126 		return -1;
127 
128 	d = strtod(str, &endptr);
129 	if (endptr != str + strlen(str))
130 		return -1;
131 
132 	*pcnt = d / 100.0;
133 	return 0;
134 }
135 
136 static int percent_slash_split(char *str, struct perf_time_interval *ptime,
137 			       u64 start, u64 end)
138 {
139 	char *p, *end_str;
140 	double pcnt, start_pcnt, end_pcnt;
141 	u64 total = end - start;
142 	int i;
143 
144 	/*
145 	 * Example:
146 	 * 10%/2: select the second 10% slice and the third 10% slice
147 	 */
148 
149 	/* We can modify this string since the original one is copied */
150 	p = strchr(str, '/');
151 	if (!p)
152 		return -1;
153 
154 	*p = '\0';
155 	if (parse_percent(&pcnt, str) < 0)
156 		return -1;
157 
158 	p++;
159 	i = (int)strtol(p, &end_str, 10);
160 	if (*end_str)
161 		return -1;
162 
163 	if (pcnt <= 0.0)
164 		return -1;
165 
166 	start_pcnt = pcnt * (i - 1);
167 	end_pcnt = pcnt * i;
168 
169 	if (start_pcnt < 0.0 || start_pcnt > 1.0 ||
170 	    end_pcnt < 0.0 || end_pcnt > 1.0) {
171 		return -1;
172 	}
173 
174 	ptime->start = start + round(start_pcnt * total);
175 	ptime->end = start + round(end_pcnt * total);
176 
177 	return 0;
178 }
179 
180 static int percent_dash_split(char *str, struct perf_time_interval *ptime,
181 			      u64 start, u64 end)
182 {
183 	char *start_str = NULL, *end_str;
184 	double start_pcnt, end_pcnt;
185 	u64 total = end - start;
186 	int ret;
187 
188 	/*
189 	 * Example: 0%-10%
190 	 */
191 
192 	ret = split_start_end(&start_str, &end_str, str, '-');
193 	if (ret || !start_str)
194 		return ret;
195 
196 	if ((parse_percent(&start_pcnt, start_str) != 0) ||
197 	    (parse_percent(&end_pcnt, end_str) != 0)) {
198 		free(start_str);
199 		return -1;
200 	}
201 
202 	free(start_str);
203 
204 	if (start_pcnt < 0.0 || start_pcnt > 1.0 ||
205 	    end_pcnt < 0.0 || end_pcnt > 1.0 ||
206 	    start_pcnt > end_pcnt) {
207 		return -1;
208 	}
209 
210 	ptime->start = start + round(start_pcnt * total);
211 	ptime->end = start + round(end_pcnt * total);
212 
213 	return 0;
214 }
215 
216 typedef int (*time_pecent_split)(char *, struct perf_time_interval *,
217 				 u64 start, u64 end);
218 
219 static int percent_comma_split(struct perf_time_interval *ptime_buf, int num,
220 			       const char *ostr, u64 start, u64 end,
221 			       time_pecent_split func)
222 {
223 	char *str, *p1, *p2;
224 	int len, ret, i = 0;
225 
226 	str = strdup(ostr);
227 	if (str == NULL)
228 		return -ENOMEM;
229 
230 	len = strlen(str);
231 	p1 = str;
232 
233 	while (p1 < str + len) {
234 		if (i >= num) {
235 			free(str);
236 			return -1;
237 		}
238 
239 		p2 = strchr(p1, ',');
240 		if (p2)
241 			*p2 = '\0';
242 
243 		ret = (func)(p1, &ptime_buf[i], start, end);
244 		if (ret < 0) {
245 			free(str);
246 			return -1;
247 		}
248 
249 		pr_debug("start time %d: %" PRIu64 ", ", i, ptime_buf[i].start);
250 		pr_debug("end time %d: %" PRIu64 "\n", i, ptime_buf[i].end);
251 
252 		i++;
253 
254 		if (p2)
255 			p1 = p2 + 1;
256 		else
257 			break;
258 	}
259 
260 	free(str);
261 	return i;
262 }
263 
264 static int one_percent_convert(struct perf_time_interval *ptime_buf,
265 			       const char *ostr, u64 start, u64 end, char *c)
266 {
267 	char *str;
268 	int len = strlen(ostr), ret;
269 
270 	/*
271 	 * c points to '%'.
272 	 * '%' should be the last character
273 	 */
274 	if (ostr + len - 1 != c)
275 		return -1;
276 
277 	/*
278 	 * Construct a string like "xx%/1"
279 	 */
280 	str = malloc(len + 3);
281 	if (str == NULL)
282 		return -ENOMEM;
283 
284 	memcpy(str, ostr, len);
285 	strcpy(str + len, "/1");
286 
287 	ret = percent_slash_split(str, ptime_buf, start, end);
288 	if (ret == 0)
289 		ret = 1;
290 
291 	free(str);
292 	return ret;
293 }
294 
295 int perf_time__percent_parse_str(struct perf_time_interval *ptime_buf, int num,
296 				 const char *ostr, u64 start, u64 end)
297 {
298 	char *c;
299 
300 	/*
301 	 * ostr example:
302 	 * 10%/2,10%/3: select the second 10% slice and the third 10% slice
303 	 * 0%-10%,30%-40%: multiple time range
304 	 * 50%: just one percent
305 	 */
306 
307 	memset(ptime_buf, 0, sizeof(*ptime_buf) * num);
308 
309 	c = strchr(ostr, '/');
310 	if (c) {
311 		return percent_comma_split(ptime_buf, num, ostr, start,
312 					   end, percent_slash_split);
313 	}
314 
315 	c = strchr(ostr, '-');
316 	if (c) {
317 		return percent_comma_split(ptime_buf, num, ostr, start,
318 					   end, percent_dash_split);
319 	}
320 
321 	c = strchr(ostr, '%');
322 	if (c)
323 		return one_percent_convert(ptime_buf, ostr, start, end, c);
324 
325 	return -1;
326 }
327 
328 struct perf_time_interval *perf_time__range_alloc(const char *ostr, int *size)
329 {
330 	const char *p1, *p2;
331 	int i = 1;
332 	struct perf_time_interval *ptime;
333 
334 	/*
335 	 * At least allocate one time range.
336 	 */
337 	if (!ostr)
338 		goto alloc;
339 
340 	p1 = ostr;
341 	while (p1 < ostr + strlen(ostr)) {
342 		p2 = strchr(p1, ',');
343 		if (!p2)
344 			break;
345 
346 		p1 = p2 + 1;
347 		i++;
348 	}
349 
350 alloc:
351 	*size = i;
352 	ptime = calloc(i, sizeof(*ptime));
353 	return ptime;
354 }
355 
356 bool perf_time__skip_sample(struct perf_time_interval *ptime, u64 timestamp)
357 {
358 	/* if time is not set don't drop sample */
359 	if (timestamp == 0)
360 		return false;
361 
362 	/* otherwise compare sample time to time window */
363 	if ((ptime->start && timestamp < ptime->start) ||
364 	    (ptime->end && timestamp > ptime->end)) {
365 		return true;
366 	}
367 
368 	return false;
369 }
370 
371 bool perf_time__ranges_skip_sample(struct perf_time_interval *ptime_buf,
372 				   int num, u64 timestamp)
373 {
374 	struct perf_time_interval *ptime;
375 	int i;
376 
377 	if ((timestamp == 0) || (num == 0))
378 		return false;
379 
380 	if (num == 1)
381 		return perf_time__skip_sample(&ptime_buf[0], timestamp);
382 
383 	/*
384 	 * start/end of multiple time ranges must be valid.
385 	 */
386 	for (i = 0; i < num; i++) {
387 		ptime = &ptime_buf[i];
388 
389 		if (timestamp >= ptime->start &&
390 		    ((timestamp < ptime->end && i < num - 1) ||
391 		     (timestamp <= ptime->end && i == num - 1))) {
392 			break;
393 		}
394 	}
395 
396 	return (i == num) ? true : false;
397 }
398 
399 int timestamp__scnprintf_usec(u64 timestamp, char *buf, size_t sz)
400 {
401 	u64  sec = timestamp / NSEC_PER_SEC;
402 	u64 usec = (timestamp % NSEC_PER_SEC) / NSEC_PER_USEC;
403 
404 	return scnprintf(buf, sz, "%"PRIu64".%06"PRIu64, sec, usec);
405 }
406 
407 int fetch_current_timestamp(char *buf, size_t sz)
408 {
409 	struct timeval tv;
410 	struct tm tm;
411 	char dt[32];
412 
413 	if (gettimeofday(&tv, NULL) || !localtime_r(&tv.tv_sec, &tm))
414 		return -1;
415 
416 	if (!strftime(dt, sizeof(dt), "%Y%m%d%H%M%S", &tm))
417 		return -1;
418 
419 	scnprintf(buf, sz, "%s%02u", dt, (unsigned)tv.tv_usec / 10000);
420 
421 	return 0;
422 }
423