summaryrefslogtreecommitdiffstats
path: root/lib/puppet/transaction/report.rb
blob: 16fee42aefdec9e83442561b9f929afe8505ac13 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
require 'puppet'
require 'puppet/indirector'

# A class for reporting what happens on each client.  Reports consist of
# two types of data:  Logs and Metrics.  Logs are the output that each
# change produces, and Metrics are all of the numerical data involved
# in the transaction.
class Puppet::Transaction::Report
  extend Puppet::Indirector

  indirects :report, :terminus_class => :processor

  attr_accessor :configuration_version
  attr_reader :resource_statuses, :logs, :metrics, :host, :time, :kind, :status

  # This is necessary since Marshall doesn't know how to
  # dump hash with default proc (see below @records)
  def self.default_format
    :yaml
  end

  def <<(msg)
    @logs << msg
    self
  end

  def add_times(name, value)
    @external_times[name] = value
  end

  def add_metric(name, hash)
    metric = Puppet::Util::Metric.new(name)

    hash.each do |name, value|
      metric.newvalue(name, value)
    end

    @metrics[metric.name] = metric
    metric
  end

  def add_resource_status(status)
    @resource_statuses[status.resource] = status
  end

  def compute_status(resource_metrics, change_metric)
    if (resource_metrics["failed"] || 0) > 0
      'failed'
    elsif change_metric > 0
      'changed'
    else
      'unchanged'
    end
  end

  def finalize_report
    resource_metrics = add_metric(:resources, calculate_resource_metrics)
    add_metric(:time, calculate_time_metrics)
    change_metric = calculate_change_metric
    add_metric(:changes, {"total" => change_metric})
    add_metric(:events, calculate_event_metrics)
    @status = compute_status(resource_metrics, change_metric)
  end

  def initialize(kind, configuration_version=nil)
    @metrics = {}
    @logs = []
    @resource_statuses = {}
    @external_times ||= {}
    @host = Puppet[:certname]
    @time = Time.now
    @kind = kind
    @report_format = 2
    @puppet_version = Puppet.version
    @configuration_version = configuration_version
    @status = 'failed' # assume failed until the report is finalized
  end

  def name
    host
  end

  # Provide a human readable textual summary of this report.
  def summary
    report = raw_summary

    ret = ""
    report.keys.sort { |a,b| a.to_s <=> b.to_s }.each do |key|
      ret += "#{Puppet::Util::Metric.labelize(key)}:\n"

      report[key].keys.sort { |a,b|
        # sort by label
        if a == :total
          1
        elsif b == :total
          -1
        else
          report[key][a].to_s <=> report[key][b].to_s
        end
      }.each do |label|
        value = report[key][label]
        next if value == 0
        value = "%0.2f" % value if value.is_a?(Float)
        ret += "   %15s %s\n" % [Puppet::Util::Metric.labelize(label) + ":", value]
      end
    end
    ret
  end

  # Provide a raw hash summary of this report.
  def raw_summary
    report = {}

    @metrics.each do |name, metric|
      key = metric.name.to_s
      report[key] = {}
      metric.values.each do |name, label, value|
        report[key][name.to_s] = value
      end
      report[key]["total"] = 0 unless key == "time" or report[key].include?("total")
    end
    (report["time"] ||= {})["last_run"] = Time.now.tv_sec
    report
  end

  # Based on the contents of this report's metrics, compute a single number
  # that represents the report. The resulting number is a bitmask where
  # individual bits represent the presence of different metrics.
  def exit_status
    status = 0
    status |= 2 if @metrics["changes"]["total"] > 0
    status |= 4 if @metrics["resources"]["failed"] > 0
    status
  end

  def to_yaml_properties
    (instance_variables - ["@external_times"]).sort
  end

  private

  def calculate_change_metric
    resource_statuses.map { |name, status| status.change_count || 0 }.inject(0) { |a,b| a+b }
  end

  def calculate_event_metrics
    metrics = Hash.new(0)
    metrics["total"] = 0
    resource_statuses.each do |name, status|
      metrics["total"] += status.events.length
      status.events.each do |event|
        metrics[event.status] += 1
      end
    end

    metrics
  end

  def calculate_resource_metrics
    metrics = Hash.new(0)
    metrics["total"] = resource_statuses.length

    resource_statuses.each do |name, status|
      Puppet::Resource::Status::STATES.each do |state|
        metrics[state.to_s] += 1 if status.send(state)
      end
    end

    metrics
  end

  def calculate_time_metrics
    metrics = Hash.new(0)
    resource_statuses.each do |name, status|
      type = Puppet::Resource.new(name).type
      metrics[type.to_s.downcase] += status.evaluation_time if status.evaluation_time
    end

    @external_times.each do |name, value|
      metrics[name.to_s.downcase] = value
    end

    metrics["total"] = metrics.values.inject(0) { |a,b| a+b }

    metrics
  end
end