brintos

brintos / linux-shallow public Read only

0
0
Text · 12.3 KiB · 1a87a0a Raw
573 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>3#define _GNU_SOURCE4#include <dirent.h>5#include <fcntl.h>6#include <getopt.h>7#include <regex.h>8#include <signal.h>9#include <stdio.h>10#include <stdlib.h>11#include <string.h>12#include <sys/stat.h>13#include <sys/signalfd.h>14#include <sys/timerfd.h>15#include <sys/types.h>16#include <sys/wait.h>17#include <time.h>18#include <unistd.h>19#include <linux/thermal.h>20 21#include <libconfig.h>22#include "thermal-tools.h"23 24#define CLASS_THERMAL "/sys/class/thermal"25 26enum {27	THERMOMETER_SUCCESS = 0,28	THERMOMETER_OPTION_ERROR,29	THERMOMETER_LOG_ERROR,30	THERMOMETER_CONFIG_ERROR,31	THERMOMETER_TIME_ERROR,32	THERMOMETER_INIT_ERROR,33	THERMOMETER_RUNTIME_ERROR34};35 36struct options {37	int loglvl;38	int logopt;39	int overwrite;40	int duration;41	const char *config;42	char postfix[PATH_MAX];43	char output[PATH_MAX];44};45 46struct tz_regex {47	regex_t regex;48	int polling;49};50 51struct configuration {52	struct tz_regex *tz_regex;53	int nr_tz_regex;54 55};56 57struct tz {58	FILE *file_out;59	int fd_temp;60	int fd_timer;61	int polling;62	const char *name;63};64 65struct thermometer {66	struct tz *tz;67	int nr_tz;68};69 70static struct tz_regex *configuration_tz_match(const char *expr,71					       struct configuration *config)72{73	int i;74 75	for (i = 0; i < config->nr_tz_regex; i++) {76 77		if (!regexec(&config->tz_regex[i].regex, expr, 0, NULL, 0))78			return &config->tz_regex[i];79	}80 81	return NULL;82}83 84static int configuration_default_init(struct configuration *config)85{86	config->tz_regex = realloc(config->tz_regex, sizeof(*config->tz_regex) *87				   (config->nr_tz_regex + 1));88 89	if (regcomp(&config->tz_regex[config->nr_tz_regex].regex, ".*",90		    REG_NOSUB | REG_EXTENDED)) {91		ERROR("Invalid regular expression\n");92		return -1;93	}94 95	config->tz_regex[config->nr_tz_regex].polling = 250;96	config->nr_tz_regex = 1;97 98	return 0;99}100 101static int configuration_init(const char *path, struct configuration *config)102{103	config_t cfg;104 105	config_setting_t *tz;106	int i, length;107 108	if (path && access(path, F_OK)) {109		ERROR("'%s' is not accessible\n", path);110		return -1;111	}112 113	if (!path && !config->nr_tz_regex) {114		INFO("No thermal zones configured, using wildcard for all of them\n");115		return configuration_default_init(config);116	}117 118	config_init(&cfg);119 120	if (!config_read_file(&cfg, path)) {121		ERROR("Failed to parse %s:%d - %s\n", config_error_file(&cfg),122		      config_error_line(&cfg), config_error_text(&cfg));123 124		return -1;125	}126 127	tz = config_lookup(&cfg, "thermal-zones");128	if (!tz) {129		ERROR("No thermal zone configured to be monitored\n");130		return -1;131	}132 133	length = config_setting_length(tz);134 135	INFO("Found %d thermal zone(s) regular expression\n", length);136 137	for (i = 0; i < length; i++) {138 139		config_setting_t *node;140		const char *name;141		int polling;142 143		node = config_setting_get_elem(tz, i);144		if (!node) {145			ERROR("Missing node name '%d'\n", i);146			return -1;147		}148 149		if (!config_setting_lookup_string(node, "name", &name)) {150			ERROR("Thermal zone name not found\n");151			return -1;152		}153 154		if (!config_setting_lookup_int(node, "polling", &polling)) {155			ERROR("Polling value not found");156			return -1;157		}158 159		config->tz_regex = realloc(config->tz_regex, sizeof(*config->tz_regex) *160					(config->nr_tz_regex + 1));161 162		if (regcomp(&config->tz_regex[config->nr_tz_regex].regex, name,163			    REG_NOSUB | REG_EXTENDED)) {164			ERROR("Invalid regular expression '%s'\n", name);165			continue;166		}167 168		config->tz_regex[config->nr_tz_regex].polling = polling;169		config->nr_tz_regex++;170 171		INFO("Thermal zone regular expression '%s' with polling %d\n",172		     name, polling);173	}174 175	return 0;176}177 178static void usage(const char *cmd)179{180	printf("%s Version: %s\n", cmd, VERSION);181	printf("Usage: %s [options]\n", cmd);182	printf("\t-h, --help\t\tthis help\n");183	printf("\t-o, --output <dir>\toutput directory for temperature capture\n");184	printf("\t-c, --config <file>\tconfiguration file\n");185	printf("\t-d, --duration <seconds>\tcapture duration\n");186	printf("\t-l, --loglevel <level>\tlog level: ");187	printf("DEBUG, INFO, NOTICE, WARN, ERROR\n");188	printf("\t-p, --postfix <string>\tpostfix to be happened at the end of the files\n");189	printf("\t-s, --syslog\t\toutput to syslog\n");190	printf("\t-w, --overwrite\t\toverwrite the temperature capture files if they exist\n");191	printf("\n");192	exit(0);193}194 195static int options_init(int argc, char *argv[], struct options *options)196{197	int opt;198	time_t now = time(NULL);199 200	struct option long_options[] = {201		{ "help",	no_argument, NULL, 'h' },202		{ "config",	required_argument, NULL, 'c' },203		{ "duration",	required_argument, NULL, 'd' },204		{ "loglevel",	required_argument, NULL, 'l' },205		{ "postfix",	required_argument, NULL, 'p' },206		{ "output",	required_argument, NULL, 'o' },207		{ "syslog",	required_argument, NULL, 's' },208		{ "overwrite",	no_argument, NULL, 'w' },209		{ 0, 0, 0, 0 }210	};211 212	strftime(options->postfix, sizeof(options->postfix),213		 "-%Y-%m-%d_%H:%M:%S", gmtime(&now));214 215	while (1) {216 217		int optindex = 0;218 219		opt = getopt_long(argc, argv, "ho:c:d:l:p:sw", long_options, &optindex);220		if (opt == -1)221			break;222 223		switch (opt) {224		case 'c':225			options->config = optarg;226			break;227		case 'd':228			options->duration = atoi(optarg) * 1000;229			break;230		case 'l':231			options->loglvl = log_str2level(optarg);232			break;233		case 'h':234			usage(basename(argv[0]));235			break;236		case 'p':237			strcpy(options->postfix, optarg);238			break;239		case 'o':240			strcpy(options->output, optarg);241			break;242		case 's':243			options->logopt = TO_SYSLOG;244			break;245		case 'w':246			options->overwrite = 1;247			break;248		default: /* '?' */249			ERROR("Usage: %s --help\n", argv[0]);250			return -1;251		}252	}253 254	return 0;255}256 257static int thermometer_add_tz(const char *path, const char *name, int polling,258			      struct thermometer *thermometer)259{260	int fd;261	char tz_path[PATH_MAX];262 263	sprintf(tz_path, CLASS_THERMAL"/%s/temp", path);264 265	fd = open(tz_path, O_RDONLY);266	if (fd < 0) {267		ERROR("Failed to open '%s': %m\n", tz_path);268		return -1;269	}270 271	thermometer->tz = realloc(thermometer->tz,272				  sizeof(*thermometer->tz) * (thermometer->nr_tz + 1));273	if (!thermometer->tz) {274		ERROR("Failed to allocate thermometer->tz\n");275		return -1;276	}277 278	thermometer->tz[thermometer->nr_tz].fd_temp = fd;279	thermometer->tz[thermometer->nr_tz].name = strdup(name);280	thermometer->tz[thermometer->nr_tz].polling = polling;281	thermometer->nr_tz++;282 283	INFO("Added thermal zone '%s->%s (polling:%d)'\n", path, name, polling);284 285	return 0;286}287 288static int thermometer_init(struct configuration *config,289			    struct thermometer *thermometer)290{291	DIR *dir;292	struct dirent *dirent;293	struct tz_regex *tz_regex;294	const char *tz_dirname = "thermal_zone";295 296	if (mainloop_init()) {297		ERROR("Failed to start mainloop\n");298		return -1;299	}300 301	dir = opendir(CLASS_THERMAL);302	if (!dir) {303		ERROR("failed to open '%s'\n", CLASS_THERMAL);304		return -1;305	}306 307	while ((dirent = readdir(dir))) {308		char tz_type[THERMAL_NAME_LENGTH];309		char tz_path[PATH_MAX];310		FILE *tz_file;311 312		if (strncmp(dirent->d_name, tz_dirname, strlen(tz_dirname)))313			continue;314 315		sprintf(tz_path, CLASS_THERMAL"/%s/type", dirent->d_name);316 317		tz_file = fopen(tz_path, "r");318		if (!tz_file) {319			ERROR("Failed to open '%s': %m", tz_path);320			continue;321		}322 323		fscanf(tz_file, "%s", tz_type);324 325		fclose(tz_file);326 327		tz_regex = configuration_tz_match(tz_type, config);328		if (!tz_regex)329			continue;330 331		if (thermometer_add_tz(dirent->d_name, tz_type,332				       tz_regex->polling, thermometer))333			continue;334	}335 336	closedir(dir);337 338	return 0;339}340 341static int timer_temperature_callback(int fd, void *arg)342{343	struct tz *tz = arg;344	char buf[16] = { 0 };345 346	pread(tz->fd_temp, buf, sizeof(buf), 0);347 348	fprintf(tz->file_out, "%ld %s", getuptimeofday_ms(), buf);349 350	read(fd, buf, sizeof(buf));351 352	return 0;353}354 355static int thermometer_start(struct thermometer *thermometer,356			     struct options *options)357{358	struct itimerspec timer_it = { 0 };359	char *path;360	FILE *f;361	int i;362 363	INFO("Capturing %d thermal zone(s) temperature...\n", thermometer->nr_tz);364 365	if (access(options->output, F_OK) && mkdir(options->output, 0700)) {366		ERROR("Failed to create directory '%s'\n", options->output);367		return -1;368	}369 370	for (i = 0; i < thermometer->nr_tz; i++) {371 372		asprintf(&path, "%s/%s%s", options->output,373			 thermometer->tz[i].name, options->postfix);374 375		if (!options->overwrite && !access(path, F_OK)) {376			ERROR("'%s' already exists\n", path);377			return -1;378		}379 380		f = fopen(path, "w");381		if (!f) {382			ERROR("Failed to create '%s':%m\n", path);383			return -1;384		}385 386		fprintf(f, "timestamp(ms) %s(°mC)\n", thermometer->tz[i].name);387 388		thermometer->tz[i].file_out = f;389 390		DEBUG("Created '%s' file for thermal zone '%s'\n", path, thermometer->tz[i].name);391 392		/*393		 * Create polling timer394		 */395		thermometer->tz[i].fd_timer = timerfd_create(CLOCK_MONOTONIC, 0);396		if (thermometer->tz[i].fd_timer < 0) {397			ERROR("Failed to create timer for '%s': %m\n",398			      thermometer->tz[i].name);399			return -1;400		}401 402		DEBUG("Watching '%s' every %d ms\n",403		      thermometer->tz[i].name, thermometer->tz[i].polling);404 405		timer_it.it_interval = timer_it.it_value =406			msec_to_timespec(thermometer->tz[i].polling);407 408		if (timerfd_settime(thermometer->tz[i].fd_timer, 0,409				    &timer_it, NULL) < 0)410			return -1;411 412		if (mainloop_add(thermometer->tz[i].fd_timer,413				 timer_temperature_callback,414				 &thermometer->tz[i]))415			return -1;416	}417 418	return 0;419}420 421static int thermometer_execute(int argc, char *argv[], char *const envp[], pid_t *pid)422{423	if (!argc)424		return 0;425 426	*pid = fork();427	if (*pid < 0) {428		ERROR("Failed to fork process: %m");429		return -1;430	}431 432	if (!(*pid)) {433		execvpe(argv[0], argv, envp);434		exit(1);435	}436 437	return 0;438}439 440static int kill_process(__maybe_unused int fd, void *arg)441{442	pid_t pid = *(pid_t *)arg;443 444	if (kill(pid, SIGTERM))445		ERROR("Failed to send SIGTERM signal to '%d': %p\n", pid);446	else if (waitpid(pid, NULL, 0))447		ERROR("Failed to wait pid '%d': %p\n", pid);448 449	mainloop_exit();450 451	return 0;452}453 454static int exit_mainloop(__maybe_unused int fd, __maybe_unused void *arg)455{456	mainloop_exit();457	return 0;458}459 460static int thermometer_wait(struct options *options, pid_t pid)461{462	int fd;463	sigset_t mask;464 465	/*466	 * If there is a duration specified, we will exit the mainloop467	 * and gracefully close all the files which will flush the468	 * file system cache469	 */470	if (options->duration) {471		struct itimerspec timer_it = { 0 };472 473		timer_it.it_value = msec_to_timespec(options->duration);474 475		fd = timerfd_create(CLOCK_MONOTONIC, 0);476		if (fd < 0) {477			ERROR("Failed to create duration timer: %m\n");478			return -1;479		}480 481		if (timerfd_settime(fd, 0, &timer_it, NULL)) {482			ERROR("Failed to set timer time: %m\n");483			return -1;484		}485 486		if (mainloop_add(fd, pid < 0 ? exit_mainloop : kill_process, &pid)) {487			ERROR("Failed to set timer exit mainloop callback\n");488			return -1;489		}490	}491 492	/*493	 * We want to catch any keyboard interrupt, as well as child494	 * signals if any in order to exit properly495	 */496	sigemptyset(&mask);497	sigaddset(&mask, SIGINT);498	sigaddset(&mask, SIGQUIT);499	sigaddset(&mask, SIGCHLD);500 501	if (sigprocmask(SIG_BLOCK, &mask, NULL)) {502		ERROR("Failed to set sigprocmask: %m\n");503		return -1;504	}505 506	fd = signalfd(-1, &mask, 0);507	if (fd < 0) {508		ERROR("Failed to set the signalfd: %m\n");509		return -1;510	}511 512	if (mainloop_add(fd, exit_mainloop, NULL)) {513		ERROR("Failed to set timer exit mainloop callback\n");514		return -1;515	}516 517	return mainloop(-1);518}519 520static int thermometer_stop(struct thermometer *thermometer)521{522	int i;523 524	INFO("Closing/flushing output files\n");525 526	for (i = 0; i < thermometer->nr_tz; i++)527		fclose(thermometer->tz[i].file_out);528 529	return 0;530}531 532int main(int argc, char *argv[], char *const envp[])533{534	struct options options = {535		.loglvl = LOG_DEBUG,536		.logopt = TO_STDOUT,537		.output = ".",538	};539	struct configuration config = { 0 };540	struct thermometer thermometer = { 0 };541 542	pid_t pid = -1;543 544	if (options_init(argc, argv, &options))545		return THERMOMETER_OPTION_ERROR;546 547	if (log_init(options.loglvl, argv[0], options.logopt))548		return THERMOMETER_LOG_ERROR;549 550	if (configuration_init(options.config, &config))551		return THERMOMETER_CONFIG_ERROR;552 553	if (uptimeofday_init())554		return THERMOMETER_TIME_ERROR;555 556	if (thermometer_init(&config, &thermometer))557		return THERMOMETER_INIT_ERROR;558 559	if (thermometer_start(&thermometer, &options))560		return THERMOMETER_RUNTIME_ERROR;561 562	if (thermometer_execute(argc - optind, &argv[optind], envp, &pid))563		return THERMOMETER_RUNTIME_ERROR;564 565	if (thermometer_wait(&options, pid))566		return THERMOMETER_RUNTIME_ERROR;567 568	if (thermometer_stop(&thermometer))569		return THERMOMETER_RUNTIME_ERROR;570 571	return THERMOMETER_SUCCESS;572}573