Move systemctl dot to systemd-analyze dot

This commit is contained in:
Simon Peeters 2013-02-13 14:17:28 -08:00 committed by Auke Kok
parent 2265fbf7e5
commit 1700761b06
7 changed files with 230 additions and 247 deletions

2
TODO
View file

@ -80,8 +80,6 @@ Features:
* introduce ExecCondition= in services
* if we have systemd-analyze in C "systemctl dot" should move there too
* EFI:
- fsck hookup for the ESP mount is missing
- write man page for efi boot generator

View file

@ -271,32 +271,6 @@
manager of the calling user.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--order</option></term>
<term><option>--require</option></term>
<listitem><para>When used in
conjunction with the
<command>dot</command> command (see
below), selects which dependencies are
shown in the dependency graph. If
<option>--order</option> is passed
only dependencies of type
<varname>After=</varname> or
<varname>Before=</varname> are
shown. If <option>--require</option>
is passed only dependencies of type
<varname>Requires=</varname>,
<varname>RequiresOverridable=</varname>,
<varname>Requisite=</varname>,
<varname>RequisiteOverridable=</varname>,
<varname>Wants=</varname> and
<varname>Conflicts=</varname> are
shown. If neither is passed, shows
dependencies of all these
types.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--no-wall</option></term>
@ -978,24 +952,6 @@
be parsed by
applications.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>dot</command></term>
<listitem><para>Generate textual
dependency graph description in dot
format for further processing with the
GraphViz
<citerefentry><refentrytitle>dot</refentrytitle><manvolnum>1</manvolnum></citerefentry>
tool. Use a command line like
<command>systemctl dot | dot -Tsvg >
systemd.svg</command> to generate a
graphical dependency tree. Unless
<option>--order</option> or
<option>--require</option> is passed
the generated graph will show both
ordering and requirement
dependencies.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>list-dependencies [NAME]</command></term>

View file

@ -57,6 +57,9 @@
<cmdsynopsis>
<command>systemd-analyze <arg choice="opt" rep="repeat">OPTIONS</arg> plot <arg choice="opt">&gt; file.svg</arg></command>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-analyze <arg choice="opt" rep="repeat">OPTIONS</arg> dot </command>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
@ -90,6 +93,17 @@
been started at what time, highlighting the time they
spent on initialization.</para>
<para><command>systemd-analyze dot</command>
Generate textual dependency graph description in dot
format for further processing with the GraphViz
<citerefentry><refentrytitle>dot</refentrytitle><manvolnum>1</manvolnum></citerefentry>
tool. Use a command line like <command>systemd-analyze
dot | dot -Tsvg > systemd.svg</command> to generate
a graphical dependency tree. Unless
<option>--order</option> or <option>--require</option>
is passed the generated graph will show both ordering
and requirement dependencies.</para>
<para>If no command is passed <command>systemd-analyze
time</command> is implied.</para>
@ -116,6 +130,32 @@
of user sessions instead of the system
manager.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--order</option></term>
<term><option>--require</option></term>
<listitem><para>When used in
conjunction with the
<command>dot</command> command (see
above), selects which dependencies are
shown in the dependency graph. If
<option>--order</option> is passed
only dependencies of type
<varname>After=</varname> or
<varname>Before=</varname> are
shown. If <option>--require</option>
is passed only dependencies of type
<varname>Requires=</varname>,
<varname>RequiresOverridable=</varname>,
<varname>Requisite=</varname>,
<varname>RequisiteOverridable=</varname>,
<varname>Wants=</varname> and
<varname>Conflicts=</varname> are
shown. If neither is passed, shows
dependencies of all these
types.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>

View file

@ -60,7 +60,7 @@ _systemctl () {
local -A OPTS=(
[STANDALONE]='--all -a --defaults --fail --ignore-dependencies --failed --force -f --full --global
--help -h --no-ask-password --no-block --no-legend --no-pager --no-reload --no-wall
--order --require --quiet -q --privileged -P --system --user --version --runtime'
--quiet -q --privileged -P --system --user --version --runtime'
[ARG]='--host -H --kill-mode --kill-who --property -p --signal -s --type -t --root'
)
@ -113,7 +113,7 @@ _systemctl () {
[JOBS]='cancel'
[SNAPSHOTS]='delete'
[ENVS]='set-environment unset-environment'
[STANDALONE]='daemon-reexec daemon-reload default dot dump
[STANDALONE]='daemon-reexec daemon-reload default dump
emergency exit halt hibernate hybrid-sleep kexec list-jobs
list-units list-unit-files poweroff reboot rescue
show-environment suspend'

View file

@ -27,8 +27,6 @@ _ctls()
'--no-legend[Do not print a legend, i.e. the column headers and the footer with hints]' \
'--no-pager[Do not pipe output into a pager]' \
'--no-ask-password[Do not ask for system passwords]' \
'--order[When generating graph for dot, show only order]' \
'--require[When generating graph for dot, show only requirement]' \
'--system[Connect to system manager]' \
'--user[Connect to user service manager]' \
'--global[Enable/disable unit files globally]' \
@ -136,6 +134,8 @@ _ctls()
_arguments \
{-h,--help}'[Show help text.]' \
'--user[Shows performance data of user sessions instead of the system manager.]' \
'--order[When generating graph for dot, show only order]' \
'--require[When generating graph for dot, show only requirement]' \
'*::systemd-analyze commands:_systemd_analyze_command'
;;
systemd-ask-password)
@ -293,6 +293,7 @@ _systemd_analyze_command(){
'time:Print the time taken to start'
'blame:prints a list of all running units, ordered by the time they took to initialize'
'plot:prints an SVG graphic detailing which system services have been started at what time'
'dot:Dump dependency graph for dot(1)'
)
if (( CURRENT == 1 )); then
@ -352,7 +353,6 @@ _outputmodes() {
"list-jobs:List jobs"
"cancel:Cancel all, one, or more jobs"
"dump:Dump server status"
"dot:Dump dependency graph for dot(1)"
"snapshot:Create a snapshot"
"delete:Remove one or more snapshots"
"show-environment:Dump environment"
@ -575,7 +575,7 @@ done
(( $+functions[_systemctl_link] )) || _systemctl_link() { _files }
# no systemctl completion for:
# [STANDALONE]='daemon-reexec daemon-reload default dot dump
# [STANDALONE]='daemon-reexec daemon-reload default dump
# emergency exit halt kexec list-jobs list-units
# list-unit-files poweroff reboot rescue show-environment'
# [NAME]='snapshot load'

View file

@ -46,6 +46,11 @@
} while(false)
static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
static enum dot {
DEP_ALL,
DEP_ORDER,
DEP_REQUIRE
} arg_dot = DEP_ALL;
double scale_x = 0.1; // pixels per ms
double scale_y = 20.0;
@ -532,6 +537,166 @@ static int analyze_time(DBusConnection *bus)
return 0;
}
static int graph_one_property(const char *name, const char *prop, DBusMessageIter *iter) {
static const char * const colors[] = {
"Requires", "[color=\"black\"]",
"RequiresOverridable", "[color=\"black\"]",
"Requisite", "[color=\"darkblue\"]",
"RequisiteOverridable", "[color=\"darkblue\"]",
"Wants", "[color=\"grey66\"]",
"Conflicts", "[color=\"red\"]",
"ConflictedBy", "[color=\"red\"]",
"After", "[color=\"green\"]"
};
const char *c = NULL;
unsigned i;
assert(name);
assert(prop);
assert(iter);
for (i = 0; i < ELEMENTSOF(colors); i += 2)
if (streq(colors[i], prop)) {
c = colors[i+1];
break;
}
if (!c)
return 0;
if (arg_dot != DEP_ALL)
if ((arg_dot == DEP_ORDER) != streq(prop, "After"))
return 0;
if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY &&
dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
DBusMessageIter sub;
dbus_message_iter_recurse(iter, &sub);
for (dbus_message_iter_recurse(iter, &sub);
dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
dbus_message_iter_next(&sub)) {
const char *s;
assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
dbus_message_iter_get_basic(&sub, &s);
printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
}
}
return 0;
}
static int graph_one(DBusConnection *bus, const struct unit_info *u) {
_cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
const char *interface = "org.freedesktop.systemd1.Unit";
int r;
DBusMessageIter iter, sub, sub2, sub3;
assert(bus);
assert(u);
r = bus_method_call_with_reply(
bus,
"org.freedesktop.systemd1",
u->unit_path,
"org.freedesktop.DBus.Properties",
"GetAll",
&reply,
NULL,
DBUS_TYPE_STRING, &interface,
DBUS_TYPE_INVALID);
if (r < 0)
return r;
if (!dbus_message_iter_init(reply, &iter) ||
dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
log_error("Failed to parse reply.");
return -EIO;
}
for (dbus_message_iter_recurse(&iter, &sub);
dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
dbus_message_iter_next(&sub)) {
const char *prop;
assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
dbus_message_iter_recurse(&sub, &sub2);
if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
log_error("Failed to parse reply.");
return -EIO;
}
dbus_message_iter_recurse(&sub2, &sub3);
r = graph_one_property(u->id, prop, &sub3);
if (r < 0)
return r;
}
return 0;
}
static int dot(DBusConnection *bus) {
_cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
DBusMessageIter iter, sub;
int r;
r = bus_method_call_with_reply(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"ListUnits",
&reply,
NULL,
DBUS_TYPE_INVALID);
if (r < 0)
return r;
if (!dbus_message_iter_init(reply, &iter) ||
dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
log_error("Failed to parse reply.");
return -EIO;
}
printf("digraph systemd {\n");
for (dbus_message_iter_recurse(&iter, &sub);
dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
dbus_message_iter_next(&sub)) {
struct unit_info u;
r = bus_parse_unit_info(&sub, &u);
if (r < 0)
return -EIO;
r = graph_one(bus, &u);
if (r < 0)
return r;
}
printf("}\n");
log_info(" Color legend: black = Requires\n"
" dark blue = Requisite\n"
" dark grey = Wants\n"
" red = Conflicts\n"
" green = After\n");
if (on_tty())
log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
"-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
return 0;
}
static void analyze_help(void)
{
printf("%s [OPTIONS...] {COMMAND} ...\n\n"
@ -539,11 +704,14 @@ static void analyze_help(void)
" -h --help Show this help\n"
" --version Show package version\n"
" --system Connect to system manager\n"
" --user Connect to user service manager\n\n"
" --user Connect to user service manager\n"
" --order When generating a dependency graph, show only order\n"
" --require When generating a dependency graph, show only requirement\n\n"
"Commands:\n"
" time print time spent in the kernel before reaching userspace\n"
" blame print list of running units ordered by time to init\n"
" plot output SVG graphic showing service initialization\n\n",
" time Print time spent in the kernel before reaching userspace\n"
" blame Print list of running units ordered by time to init\n"
" plot Output SVG graphic showing service initialization\n"
" dot Dump dependency graph (in dot(1) format)\n\n",
program_invocation_short_name);
}
@ -551,6 +719,8 @@ static int parse_argv(int argc, char *argv[])
{
enum {
ARG_VERSION = 0x100,
ARG_ORDER,
ARG_REQUIRE,
ARG_USER,
ARG_SYSTEM
};
@ -558,6 +728,8 @@ static int parse_argv(int argc, char *argv[])
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "order", no_argument, NULL, ARG_ORDER },
{ "require", no_argument, NULL, ARG_REQUIRE },
{ "user", no_argument, NULL, ARG_USER },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ NULL, 0, NULL, 0 }
@ -580,6 +752,12 @@ static int parse_argv(int argc, char *argv[])
case ARG_SYSTEM:
arg_scope = UNIT_FILE_SYSTEM;
break;
case ARG_ORDER:
arg_dot = DEP_ORDER;
break;
case ARG_REQUIRE:
arg_dot = DEP_REQUIRE;
break;
case -1:
return 1;
case '?':
@ -614,6 +792,8 @@ int main(int argc, char *argv[]) {
r = analyze_blame(bus);
else if (streq(argv[optind], "plot"))
r = analyze_plot(bus);
else if (streq(argv[optind], "dot"))
r = dot(bus);
else
log_error("Unknown operation '%s'.", argv[optind]);

View file

@ -116,11 +116,6 @@ static enum action {
ACTION_CANCEL_SHUTDOWN,
_ACTION_MAX
} arg_action = ACTION_SYSTEMCTL;
static enum dot {
DOT_ALL,
DOT_ORDER,
DOT_REQUIRE
} arg_dot = DOT_ALL;
static enum transport {
TRANSPORT_NORMAL,
TRANSPORT_SSH,
@ -891,176 +886,6 @@ static int list_dependencies(DBusConnection *bus, char **args) {
return list_dependencies_one(bus, u, 0, NULL, 0);
}
static int dot_one_property(const char *name, const char *prop, DBusMessageIter *iter) {
static const char * const colors[] = {
"Requires", "[color=\"black\"]",
"RequiresOverridable", "[color=\"black\"]",
"Requisite", "[color=\"darkblue\"]",
"RequisiteOverridable", "[color=\"darkblue\"]",
"Wants", "[color=\"grey66\"]",
"Conflicts", "[color=\"red\"]",
"ConflictedBy", "[color=\"red\"]",
"After", "[color=\"green\"]"
};
const char *c = NULL;
unsigned i;
assert(name);
assert(prop);
assert(iter);
for (i = 0; i < ELEMENTSOF(colors); i += 2)
if (streq(colors[i], prop)) {
c = colors[i+1];
break;
}
if (!c)
return 0;
if (arg_dot != DOT_ALL)
if ((arg_dot == DOT_ORDER) != streq(prop, "After"))
return 0;
switch (dbus_message_iter_get_arg_type(iter)) {
case DBUS_TYPE_ARRAY:
if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
DBusMessageIter sub;
dbus_message_iter_recurse(iter, &sub);
while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
const char *s;
assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
dbus_message_iter_get_basic(&sub, &s);
printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
dbus_message_iter_next(&sub);
}
return 0;
}
}
return 0;
}
static int dot_one(DBusConnection *bus, const struct unit_info *u) {
_cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
const char *interface = "org.freedesktop.systemd1.Unit";
int r;
DBusMessageIter iter, sub, sub2, sub3;
assert(bus);
assert(u);
r = bus_method_call_with_reply(
bus,
"org.freedesktop.systemd1",
u->unit_path,
"org.freedesktop.DBus.Properties",
"GetAll",
&reply,
NULL,
DBUS_TYPE_STRING, &interface,
DBUS_TYPE_INVALID);
if (r < 0)
return r;
if (!dbus_message_iter_init(reply, &iter) ||
dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
log_error("Failed to parse reply.");
return -EIO;
}
dbus_message_iter_recurse(&iter, &sub);
while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
const char *prop;
assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
dbus_message_iter_recurse(&sub, &sub2);
if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
log_error("Failed to parse reply.");
return -EIO;
}
dbus_message_iter_recurse(&sub2, &sub3);
r = dot_one_property(u->id, prop, &sub3);
if (r < 0)
return r;
dbus_message_iter_next(&sub);
}
return 0;
}
static int dot(DBusConnection *bus, char **args) {
_cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
DBusMessageIter iter, sub;
int r;
r = bus_method_call_with_reply(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"ListUnits",
&reply,
NULL,
DBUS_TYPE_INVALID);
if (r < 0)
return r;
if (!dbus_message_iter_init(reply, &iter) ||
dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
log_error("Failed to parse reply.");
return -EIO;
}
printf("digraph systemd {\n");
dbus_message_iter_recurse(&iter, &sub);
while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
struct unit_info u;
r = bus_parse_unit_info(&sub, &u);
if (r < 0)
return -EIO;
r = dot_one(bus, &u);
if (r < 0)
return r;
/* printf("\t\"%s\";\n", u.id); */
dbus_message_iter_next(&sub);
}
printf("}\n");
log_info(" Color legend: black = Requires\n"
" dark blue = Requisite\n"
" dark grey = Wants\n"
" red = Conflicts\n"
" green = After\n");
if (on_tty())
log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
"-- Try a shell pipeline like 'systemctl dot | dot -Tsvg > systemd.svg'!\n");
return 0;
}
static int list_jobs(DBusConnection *bus, char **args) {
_cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
DBusMessageIter iter, sub, sub2;
@ -4276,8 +4101,6 @@ static int systemctl_help(void) {
" --no-pager Do not pipe output into a pager\n"
" --no-ask-password\n"
" Do not ask for system passwords\n"
" --order When generating graph for dot, show only order\n"
" --require When generating graph for dot, show only requirement\n"
" --system Connect to system manager\n"
" --user Connect to user service manager\n"
" --global Enable/disable unit files globally\n"
@ -4337,7 +4160,6 @@ static int systemctl_help(void) {
" cancel [JOB...] Cancel all, one, or more jobs\n\n"
"Status Commands:\n"
" dump Dump server status\n"
" dot Dump dependency graph for dot(1)\n\n"
"Snapshot Commands:\n"
" snapshot [NAME] Create a snapshot\n"
" delete [NAME...] Remove one or more snapshots\n\n"
@ -4460,8 +4282,6 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
ARG_NO_LEGEND,
ARG_NO_PAGER,
ARG_NO_WALL,
ARG_ORDER,
ARG_REQUIRE,
ARG_ROOT,
ARG_FULL,
ARG_NO_RELOAD,
@ -4491,8 +4311,6 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-wall", no_argument, NULL, ARG_NO_WALL },
{ "quiet", no_argument, NULL, 'q' },
{ "order", no_argument, NULL, ARG_ORDER },
{ "require", no_argument, NULL, ARG_REQUIRE },
{ "root", required_argument, NULL, ARG_ROOT },
{ "force", no_argument, NULL, ARG_FORCE },
{ "no-reload", no_argument, NULL, ARG_NO_RELOAD },
@ -4599,14 +4417,6 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
arg_no_wall = true;
break;
case ARG_ORDER:
arg_dot = DOT_ORDER;
break;
case ARG_REQUIRE:
arg_dot = DOT_REQUIRE;
break;
case ARG_ROOT:
arg_root = optarg;
break;
@ -5307,7 +5117,6 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError
{ "status", MORE, 2, show },
{ "help", MORE, 2, show },
{ "dump", EQUAL, 1, dump },
{ "dot", EQUAL, 1, dot },
{ "snapshot", LESS, 2, snapshot },
{ "delete", MORE, 2, delete_snapshot },
{ "daemon-reload", EQUAL, 1, daemon_reload },