Skip to content

Commit 2c04442

Browse files
committed
Add rc-analyze
Adds a commandline utility to view various runtime statistics using the event log Closes: #997
1 parent 94593b0 commit 2c04442

6 files changed

Lines changed: 721 additions & 33 deletions

File tree

man/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ man3 = [
1212
man8 = [
1313
'openrc.8',
1414
'openrc-run.8',
15+
'rc-analyze.8',
1516
'rc-service.8',
1617
'rc-status.8',
1718
'rc-update.8',

man/rc-analyze.8

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
.\" Copyright (c) 2026 The OpenRC Authors.
2+
.\" See the Authors file at the top-level directory of this distribution and
3+
.\" https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS
4+
.\"
5+
.\" This file is part of OpenRC. It is subject to the license terms in
6+
.\" the LICENSE file found in the top-level directory of this
7+
.\" distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE
8+
.\" This file may not be copied, modified, propagated, or distributed
9+
.\" except according to the terms contained in the LICENSE file.
10+
.\"
11+
.Dd April 2, 2026
12+
.Dt RC-ANALYZE 8 SMM
13+
.Os OpenRC
14+
.Sh NAME
15+
.Nm rc-analyze
16+
.Nd analyze OpenRC startup performance
17+
.Sh SYNOPSIS
18+
.Nm
19+
.Ar blame | critical-chain | time
20+
.Sh DESCRIPTION
21+
The
22+
.Nm
23+
utility provides commands to analyze the boot performance of an OpenRC-based
24+
system.
25+
.Sh COMMANDS
26+
The following commands are available:
27+
.Bl -tag -width ".Fl critical-chain"
28+
.It Nm blame
29+
Prints a list of all services that were started, sorted by the time they took
30+
to start up (longest first). This helps identify the slowest services during
31+
boot.
32+
.It Nm critical-chain
33+
.Op Ar service
34+
Shows the chain of dependencies that led to the specified
35+
.Ar service
36+
starting. If no service is specified, it shows the chain for the last service
37+
to start in the default runlevel. The output is a tree where each dependency
38+
is a parent of the service that depends on it. The time at which each service
39+
became active and the time it took to start are printed.
40+
.It Nm time
41+
Prints a summary of the time spent getting to the default runlevel.
42+
.El
43+
.Sh SEE ALSO
44+
.Xr openrc 8 ,
45+
.Sh AUTHORS
46+
.An The OpenRC Authors <openrc@lists.gentoo.org>

src/librc/rc-eventlog.c

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
#include <errno.h>
1919
#include <fcntl.h>
20+
#include <inttypes.h>
21+
#include <stdint.h>
2022
#include <stdio.h>
2123
#include <stdlib.h>
2224
#include <string.h>
@@ -47,26 +49,15 @@ static const char *rc_service_state_name(RC_SERVICE state)
4749
}
4850

4951
/*
50-
* Format the current time as an ISO 8601 timestamp with milliseconds.
51-
* Returns a dynamically allocated string that must be freed by the caller.
52+
* Get the current monotonic time in milliseconds.
5253
*/
53-
static char *format_timestamp(void)
54+
static int64_t tm_now(void)
5455
{
55-
time_t now;
56-
struct tm *tm;
57-
char timebuf[32];
58-
char *result;
59-
int ms;
56+
struct timespec tv;
57+
int64_t sec_to_ms = 1000, round_up = 500000, ns_to_ms = 1000000;
6058

61-
clock_gettime(CLOCK_REALTIME, &ts);
62-
tm = localtime(&ts.tv_sec);
63-
ms = (int)(ts.tv_nsec / 1000000);
64-
65-
strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S", tm);
66-
xasprintf(&result, "%s.%03d%+03ld%02ld", timebuf, ms,
67-
tm->tm_gmtoff / 3600, (labs(tm->tm_gmtoff) % 3600) / 60);
68-
69-
return result;
59+
clock_gettime(CLOCK_MONOTONIC, &tv);
60+
return (tv.tv_sec * sec_to_ms) + ((tv.tv_nsec + round_up) / ns_to_ms);
7061
}
7162

7263
/*
@@ -96,7 +87,6 @@ void rc_eventlog_service(const char *service, RC_SERVICE state)
9687
{
9788
int svcfd;
9889
int eventsfd;
99-
char *timestamp;
10090
FILE *fp;
10191
const char *state_name;
10292

@@ -116,21 +106,16 @@ void rc_eventlog_service(const char *service, RC_SERVICE state)
116106

117107
service = basename_c(service);
118108

119-
timestamp = format_timestamp();
120-
121109
state_name = rc_service_state_name(state);
122110

123111
fp = do_fopenat(eventsfd, service, O_WRONLY | O_CREAT | O_APPEND);
124112
close(eventsfd);
125113

126-
if (!fp) {
127-
free(timestamp);
114+
if (!fp)
128115
return;
129-
}
130116

131-
fprintf(fp, "%s %s\n", timestamp, state_name);
117+
fprintf(fp, "%" PRId64 " %s\n", tm_now(), state_name);
132118
fclose(fp);
133-
free(timestamp);
134119
}
135120

136121
/*
@@ -139,7 +124,6 @@ void rc_eventlog_service(const char *service, RC_SERVICE state)
139124
void rc_eventlog_global(const char *event_type, const char *message)
140125
{
141126
int svcfd;
142-
char *timestamp;
143127
FILE *fp;
144128

145129
if (!event_type || !message)
@@ -152,15 +136,10 @@ void rc_eventlog_global(const char *event_type, const char *message)
152136
if (rc_eventlog_init() != 0)
153137
return;
154138

155-
timestamp = format_timestamp();
156-
157139
fp = do_fopenat(svcfd, RC_EVENTLOG_GLOBAL, O_WRONLY | O_CREAT | O_APPEND);
158-
if (!fp) {
159-
free(timestamp);
140+
if (!fp)
160141
return;
161-
}
162142

163-
fprintf(fp, "%s %s %s\n", timestamp, event_type, message);
143+
fprintf(fp, "%" PRId64 " %s %s\n", tm_now(), event_type, message);
164144
fclose(fp);
165-
free(timestamp);
166145
}

src/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ subdir('openrc-user')
2222
subdir('pam_openrc')
2323
subdir('poweroff')
2424
subdir('rc-abort')
25+
subdir('rc-analyze')
2526
subdir('rc-depend')
2627
subdir('rc-service')
2728
subdir('rc-sstat')

src/rc-analyze/meson.build

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
libm = cc.find_library('m', required: true)
2+
3+
executable('rc-analyze', 'rc-analyze.c',
4+
dependencies: [rc, einfo, shared, libm],
5+
include_directories: incdir,
6+
install: true,
7+
install_dir: bindir)

0 commit comments

Comments
 (0)