1 package System::Introspector::State;
3 use File::Tree::Snapshot;
4 use System::Introspector::Gatherer;
5 use Object::Remote::Future qw( await_all );
7 use JSON::Diffable qw( encode_json );
9 has config => (is => 'ro', required => 1);
11 has root => (is => 'ro', required => 1);
13 sub user { $_[0]->config->user }
15 sub sudo_user { $_[0]->config->sudo_user }
17 sub _log { shift; printf "[%s] %s\n", scalar(localtime), join '', @_ }
20 my ($self, @groups) = @_;
22 for my $group (@groups) {
24 for my $host ($self->config->hosts) {
25 $self->_log("Beginning to fetch group '$group' on '$host'");
26 push @waiting, [$host, $self->fetch($host, $group)];
28 $self->_log("Now waiting for results");
29 for my $wait (@waiting) {
30 my ($host, @futures) = @$_;
31 my @data = await_all @futures;
32 $self->_log("Received all from group '$group' on '$host'");
33 $self->_store($host, $group, +{ map %$_, @data });
41 my ($self, $group) = @_;
42 return $self->config->config_for_group($group)->{introspect};
46 my ($self, $host, $group) = @_;
47 my $spec = $self->introspectors($group);
49 push(@{ $spec->{$_}{sudo} ? \@sudo : \@nosudo}, [$_, $spec->{$_}])
53 $self->_log("Without sudo: ", join ", ", map $_->[0], @nosudo);
54 my $proxy = $self->_create_gatherer(
56 introspectors => [@nosudo],
58 push @futures, $proxy->start::gather_all;
61 $self->_log("With sudo: ", join ", ", map $_->[0], @nosudo);
62 my $proxy = $self->_create_gatherer(
65 introspectors => [@sudo],
67 push @futures, $proxy->start::gather_all;
73 my ($self, @path) = @_;
74 my $storage = File::Tree::Snapshot->new(
76 storage_path => join('/', $self->root, @path),
79 unless $storage->exists;
84 my ($self, $host, $group, $gathered) = @_;
85 $self->_log("Storing data for group '$group' on '$host'");
86 my $storage = $self->storage($host, $group);
89 for my $class (sort keys %$gathered) {
90 my $file = sprintf '%s.json', join '/',
92 s{([a-z0-9])([A-Z])}{${1}_${2}}g;
94 } split m{::}, $class;
95 my $fh = $storage->open('>:utf8', $file, mkpath => 1);
96 my $full_path = $storage->file($file);
97 $self->_log("Writing $full_path");
98 print $fh encode_json($gathered->{$class});
99 push @files, $full_path;
101 $self->_cleanup($storage, [@files]);
102 $self->_log("Committing");
106 $self->_log("Rolling back snapshot because of: ", $@ || 'unknown error');
114 my ($self, $storage, $known_files) = @_;
115 my %known = map { ($_ => 1) } @$known_files;
116 my @files = $storage->find_files('json');
117 for my $file (@files) {
118 next if $known{$file};
119 $self->_log("Removing $file");
121 or die "Unable to remove '$file': $!\n";
126 sub _create_gatherer {
127 my ($self, %arg) = @_;
128 return System::Introspector::Gatherer->new_from_spec(
131 sudo_user => $arg{sudo} && $self->sudo_user,
132 introspectors => $arg{introspectors},
140 System::Introspector::State - Gather system state
144 my $state = System::Introspector::State->new(
145 host => 'foo.example.com',
146 storage => $storage_obj,
148 introspect => [qw( ProbeName )],
152 my $data = $state->fetch;
153 $state->fetch_and_store;
157 Gathers system introspection data based on configuration and stores
158 it with a L<File::Tree::Snapshot> object.
164 A hash reference containing a C<introspect> key with an array reference
165 value containing a list of probe names without the
166 C<System::Introspector::Probe::> prefix. This attribute is required.
170 An optional hostname. If no hostname is supplied, the local configuration
171 data will be fetched.
175 A L<File::Tree::Snapshot> object.
181 my $data = $state->fetch;
183 Fetches all probe data.
185 =head2 fetch_and_store
187 $state->fetch_and_store;
189 Fetches all probe data and stores it in the L</storage>.