1 // SPDX-License-Identifier: GPL-2.0-only
2 // Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
13 #include <sys/signalfd.h>
14 #include <sys/timerfd.h>
15 #include <sys/types.h>
19 #include <linux/thermal.h>
21 #include <libconfig.h>
22 #include "thermal-tools.h"
24 #define CLASS_THERMAL "/sys/class/thermal"
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_ERROR
42 char postfix
[PATH_MAX
];
43 char output
[PATH_MAX
];
51 struct configuration
{
52 struct tz_regex
*tz_regex
;
70 static struct tz_regex
*configuration_tz_match(const char *expr
,
71 struct configuration
*config
)
75 for (i
= 0; i
< config
->nr_tz_regex
; i
++) {
77 if (!regexec(&config
->tz_regex
[i
].regex
, expr
, 0, NULL
, 0))
78 return &config
->tz_regex
[i
];
84 static int configuration_default_init(struct configuration
*config
)
86 config
->tz_regex
= realloc(config
->tz_regex
, sizeof(*config
->tz_regex
) *
87 (config
->nr_tz_regex
+ 1));
89 if (regcomp(&config
->tz_regex
[config
->nr_tz_regex
].regex
, ".*",
90 REG_NOSUB
| REG_EXTENDED
)) {
91 ERROR("Invalid regular expression\n");
95 config
->tz_regex
[config
->nr_tz_regex
].polling
= 250;
96 config
->nr_tz_regex
= 1;
101 static int configuration_init(const char *path
, struct configuration
*config
)
105 config_setting_t
*tz
;
108 if (path
&& access(path
, F_OK
)) {
109 ERROR("'%s' is not accessible\n", path
);
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
);
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
));
127 tz
= config_lookup(&cfg
, "thermal-zones");
129 ERROR("No thermal zone configured to be monitored\n");
133 length
= config_setting_length(tz
);
135 INFO("Found %d thermal zone(s) regular expression\n", length
);
137 for (i
= 0; i
< length
; i
++) {
139 config_setting_t
*node
;
143 node
= config_setting_get_elem(tz
, i
);
145 ERROR("Missing node name '%d'\n", i
);
149 if (!config_setting_lookup_string(node
, "name", &name
)) {
150 ERROR("Thermal zone name not found\n");
154 if (!config_setting_lookup_int(node
, "polling", &polling
)) {
155 ERROR("Polling value not found");
159 config
->tz_regex
= realloc(config
->tz_regex
, sizeof(*config
->tz_regex
) *
160 (config
->nr_tz_regex
+ 1));
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
);
168 config
->tz_regex
[config
->nr_tz_regex
].polling
= polling
;
169 config
->nr_tz_regex
++;
171 INFO("Thermal zone regular expression '%s' with polling %d\n",
178 static void usage(const char *cmd
)
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");
195 static int options_init(int argc
, char *argv
[], struct options
*options
)
198 time_t now
= time(NULL
);
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' },
212 strftime(options
->postfix
, sizeof(options
->postfix
),
213 "-%Y-%m-%d_%H:%M:%S", gmtime(&now
));
219 opt
= getopt_long(argc
, argv
, "ho:c:d:l:p:sw", long_options
, &optindex
);
225 options
->config
= optarg
;
228 options
->duration
= atoi(optarg
) * 1000;
231 options
->loglvl
= log_str2level(optarg
);
234 usage(basename(argv
[0]));
237 strcpy(options
->postfix
, optarg
);
240 strcpy(options
->output
, optarg
);
243 options
->logopt
= TO_SYSLOG
;
246 options
->overwrite
= 1;
249 ERROR("Usage: %s --help\n", argv
[0]);
257 static int thermometer_add_tz(const char *path
, const char *name
, int polling
,
258 struct thermometer
*thermometer
)
261 char tz_path
[PATH_MAX
];
264 sprintf(tz_path
, CLASS_THERMAL
"/%s/temp", path
);
266 fd
= open(tz_path
, O_RDONLY
);
268 ERROR("Failed to open '%s': %m\n", tz_path
);
272 tz
= realloc(thermometer
->tz
, sizeof(*thermometer
->tz
) * (thermometer
->nr_tz
+ 1));
274 ERROR("Failed to allocate thermometer->tz\n");
278 thermometer
->tz
= tz
;
279 thermometer
->tz
[thermometer
->nr_tz
].fd_temp
= fd
;
280 thermometer
->tz
[thermometer
->nr_tz
].name
= strdup(name
);
281 thermometer
->tz
[thermometer
->nr_tz
].polling
= polling
;
282 thermometer
->nr_tz
++;
284 INFO("Added thermal zone '%s->%s (polling:%d)'\n", path
, name
, polling
);
289 static int thermometer_init(struct configuration
*config
,
290 struct thermometer
*thermometer
)
293 struct dirent
*dirent
;
294 struct tz_regex
*tz_regex
;
295 const char *tz_dirname
= "thermal_zone";
297 if (mainloop_init()) {
298 ERROR("Failed to start mainloop\n");
302 dir
= opendir(CLASS_THERMAL
);
304 ERROR("failed to open '%s'\n", CLASS_THERMAL
);
308 while ((dirent
= readdir(dir
))) {
309 char tz_type
[THERMAL_NAME_LENGTH
];
310 char tz_path
[PATH_MAX
];
313 if (strncmp(dirent
->d_name
, tz_dirname
, strlen(tz_dirname
)))
316 sprintf(tz_path
, CLASS_THERMAL
"/%s/type", dirent
->d_name
);
318 tz_file
= fopen(tz_path
, "r");
320 ERROR("Failed to open '%s': %m", tz_path
);
324 fscanf(tz_file
, "%s", tz_type
);
328 tz_regex
= configuration_tz_match(tz_type
, config
);
332 if (thermometer_add_tz(dirent
->d_name
, tz_type
,
333 tz_regex
->polling
, thermometer
))
342 static int timer_temperature_callback(int fd
, void *arg
)
345 char buf
[16] = { 0 };
347 pread(tz
->fd_temp
, buf
, sizeof(buf
), 0);
349 fprintf(tz
->file_out
, "%ld %s", getuptimeofday_ms(), buf
);
351 read(fd
, buf
, sizeof(buf
));
356 static int thermometer_start(struct thermometer
*thermometer
,
357 struct options
*options
)
359 struct itimerspec timer_it
= { 0 };
364 INFO("Capturing %d thermal zone(s) temperature...\n", thermometer
->nr_tz
);
366 if (access(options
->output
, F_OK
) && mkdir(options
->output
, 0700)) {
367 ERROR("Failed to create directory '%s'\n", options
->output
);
371 for (i
= 0; i
< thermometer
->nr_tz
; i
++) {
373 asprintf(&path
, "%s/%s%s", options
->output
,
374 thermometer
->tz
[i
].name
, options
->postfix
);
376 if (!options
->overwrite
&& !access(path
, F_OK
)) {
377 ERROR("'%s' already exists\n", path
);
381 f
= fopen(path
, "w");
383 ERROR("Failed to create '%s':%m\n", path
);
387 fprintf(f
, "timestamp(ms) %s(°mC)\n", thermometer
->tz
[i
].name
);
389 thermometer
->tz
[i
].file_out
= f
;
391 DEBUG("Created '%s' file for thermal zone '%s'\n", path
, thermometer
->tz
[i
].name
);
394 * Create polling timer
396 thermometer
->tz
[i
].fd_timer
= timerfd_create(CLOCK_MONOTONIC
, 0);
397 if (thermometer
->tz
[i
].fd_timer
< 0) {
398 ERROR("Failed to create timer for '%s': %m\n",
399 thermometer
->tz
[i
].name
);
403 DEBUG("Watching '%s' every %d ms\n",
404 thermometer
->tz
[i
].name
, thermometer
->tz
[i
].polling
);
406 timer_it
.it_interval
= timer_it
.it_value
=
407 msec_to_timespec(thermometer
->tz
[i
].polling
);
409 if (timerfd_settime(thermometer
->tz
[i
].fd_timer
, 0,
410 &timer_it
, NULL
) < 0)
413 if (mainloop_add(thermometer
->tz
[i
].fd_timer
,
414 timer_temperature_callback
,
415 &thermometer
->tz
[i
]))
422 static int thermometer_execute(int argc
, char *argv
[], char *const envp
[], pid_t
*pid
)
429 ERROR("Failed to fork process: %m");
434 execvpe(argv
[0], argv
, envp
);
441 static int kill_process(__maybe_unused
int fd
, void *arg
)
443 pid_t pid
= *(pid_t
*)arg
;
445 if (kill(pid
, SIGTERM
))
446 ERROR("Failed to send SIGTERM signal to '%d': %p\n", pid
);
447 else if (waitpid(pid
, NULL
, 0))
448 ERROR("Failed to wait pid '%d': %p\n", pid
);
455 static int exit_mainloop(__maybe_unused
int fd
, __maybe_unused
void *arg
)
461 static int thermometer_wait(struct options
*options
, pid_t pid
)
467 * If there is a duration specified, we will exit the mainloop
468 * and gracefully close all the files which will flush the
471 if (options
->duration
) {
472 struct itimerspec timer_it
= { 0 };
474 timer_it
.it_value
= msec_to_timespec(options
->duration
);
476 fd
= timerfd_create(CLOCK_MONOTONIC
, 0);
478 ERROR("Failed to create duration timer: %m\n");
482 if (timerfd_settime(fd
, 0, &timer_it
, NULL
)) {
483 ERROR("Failed to set timer time: %m\n");
487 if (mainloop_add(fd
, pid
< 0 ? exit_mainloop
: kill_process
, &pid
)) {
488 ERROR("Failed to set timer exit mainloop callback\n");
494 * We want to catch any keyboard interrupt, as well as child
495 * signals if any in order to exit properly
498 sigaddset(&mask
, SIGINT
);
499 sigaddset(&mask
, SIGQUIT
);
500 sigaddset(&mask
, SIGCHLD
);
502 if (sigprocmask(SIG_BLOCK
, &mask
, NULL
)) {
503 ERROR("Failed to set sigprocmask: %m\n");
507 fd
= signalfd(-1, &mask
, 0);
509 ERROR("Failed to set the signalfd: %m\n");
513 if (mainloop_add(fd
, exit_mainloop
, NULL
)) {
514 ERROR("Failed to set timer exit mainloop callback\n");
521 static int thermometer_stop(struct thermometer
*thermometer
)
525 INFO("Closing/flushing output files\n");
527 for (i
= 0; i
< thermometer
->nr_tz
; i
++)
528 fclose(thermometer
->tz
[i
].file_out
);
533 int main(int argc
, char *argv
[], char *const envp
[])
535 struct options options
= {
540 struct configuration config
= { 0 };
541 struct thermometer thermometer
= { 0 };
545 if (options_init(argc
, argv
, &options
))
546 return THERMOMETER_OPTION_ERROR
;
548 if (log_init(options
.loglvl
, argv
[0], options
.logopt
))
549 return THERMOMETER_LOG_ERROR
;
551 if (configuration_init(options
.config
, &config
))
552 return THERMOMETER_CONFIG_ERROR
;
554 if (uptimeofday_init())
555 return THERMOMETER_TIME_ERROR
;
557 if (thermometer_init(&config
, &thermometer
))
558 return THERMOMETER_INIT_ERROR
;
560 if (thermometer_start(&thermometer
, &options
))
561 return THERMOMETER_RUNTIME_ERROR
;
563 if (thermometer_execute(argc
- optind
, &argv
[optind
], envp
, &pid
))
564 return THERMOMETER_RUNTIME_ERROR
;
566 if (thermometer_wait(&options
, pid
))
567 return THERMOMETER_RUNTIME_ERROR
;
569 if (thermometer_stop(&thermometer
))
570 return THERMOMETER_RUNTIME_ERROR
;
572 return THERMOMETER_SUCCESS
;