Trend.gather_data()   F
last analyzed

Complexity

Conditions 9

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 9
dl 0
loc 44
rs 3
1
# vim: set expandtab sw=4 ts=4:
2
# disable pylint message about unused variable 'fig'
3
# pylint: disable=unused-variable
4
"""
5
Generate a chart from the gathered buildtime data.
6
7
Copyright (C) 2014-2016 Dieter Adriaenssens <[email protected]>
8
9
This file is part of buildtimetrend/python-lib
10
<https://github.com/buildtimetrend/python-lib/>
11
12
This program is free software: you can redistribute it and/or modify
13
it under the terms of the GNU Affero General Public License as published by
14
the Free Software Foundation, either version 3 of the License, or
15
any later version.
16
17
This program is distributed in the hope that it will be useful,
18
but WITHOUT ANY WARRANTY; without even the implied warranty of
19
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
GNU Affero General Public License for more details.
21
22
You should have received a copy of the GNU Affero General Public License
23
along with this program. If not, see <http://www.gnu.org/licenses/>.
24
"""
25
26
from collections import OrderedDict
27
from lxml import etree
28
import matplotlib
29
# Force matplotlib to not use any Xwindow backend.
30
matplotlib.use('Agg')
31
from matplotlib import pyplot as plt
32
from buildtimetrend import logger
33
from buildtimetrend.tools import check_file
34
35
36
class Trend(object):
37
38
    """Trend class, generate a chart from gathered buildtime data."""
39
40
    def __init__(self):
41
        """Initialize instance."""
42
        self.stages = {}
43
        self.builds = []
44
45
    def gather_data(self, result_file):
46
        """
47
        Get buildtime data from an xml file.
48
49
        Parameters
50
        - result_file : xml file containing the buildtime data
51
        """
52
        # load buildtimes file
53
        if check_file(result_file):
54
            root_xml = etree.parse(result_file).getroot()
55
        else:
56
            return False
57
58
        index = 0
59
        # print content of buildtimes file
60
        for build_xml in root_xml:
61
            build_id = build_xml.get('build')
62
            job_id = build_xml.get('job')
63
64
            if job_id is None and build_id is None:
65
                build_name = "#{0:d}".format(index + 1)
66
            elif job_id is not None:
67
                build_name = job_id
68
            else:
69
                build_name = build_id
70
71
            self.builds.append(build_name)
72
73
            # add 0 to each existing stage, to make sure that
74
            # the indexes of each value
75
            # are correct, even if a stage does not exist in a build
76
            # if a stage exists, the zero will be replaced by its duration
77
            for stage in self.stages:
78
                self.stages[stage].append(0)
79
80
            # add duration of each stage to stages list
81
            for build_child in build_xml:
82
                if build_child.tag == 'stages':
83
                    stage_count = len(build_child)
84
                    self.parse_xml_stages(build_child, index)
85
            logger.debug("Build ID : %s, Job : %s, stages : %d",
86
                         build_id, job_id, stage_count)
87
            index += 1
88
        return True
89
90
    def parse_xml_stages(self, stages, index):
91
        """Parse stages in from xml file."""
92
        for stage in stages:
93
            if (stage.tag == 'stage' and
94
                    stage.get('name') is not None and
95
                    stage.get('duration') is not None):
96
                if stage.get('name') in self.stages:
97
                    temp_dict = self.stages[stage.get('name')]
98
                else:
99
                    # when a new stage is added,
100
                    # create list with zeros,
101
                    # one for each existing build
102
                    temp_dict = [0] * (index + 1)
103
                temp_dict[index] = float(stage.get('duration'))
104
                self.stages[stage.get('name')] = temp_dict
105
106
    def generate(self, trend_file):
107
        """
108
        Generate the trend chart and save it as a PNG image using matplotlib.
109
110
        Parameters
111
        - trend_file : file name to save chart image to
112
        """
113
        fig, axes = plt.subplots()
114
115
        # sort stages by key
116
        stages = OrderedDict(sorted(self.stages.items()))
117
118
        # add data
119
        x_values = list(range(len(self.builds)))
120
        y_values = list(stages.values())
121
        plots = plt.stackplot(x_values, y_values)
122
        plt.xticks(x_values, self.builds, rotation=45, size=10)
123
124
        # label axes and add graph title
125
        axes.set_xlabel("Builds", {'fontsize': 14})
126
        axes.xaxis.set_label_coords(1.05, -0.05)
127
        axes.set_ylabel("Duration [s]", {'fontsize': 14})
128
        axes.set_title("Build stages trend", {'fontsize': 22})
129
130
        # display legend
131
        legend_proxies = []
132
        for plot in plots:
133
            legend_proxies.append(
134
                plt.Rectangle((0, 0), 1, 1, fc=plot.get_facecolor()[0]))
135
        # add legend in reverse order, in upper left corner
136
        axes.legend(
137
            legend_proxies[::-1],
138
            list(stages.keys())[::-1],
139
            loc=2
140
        )
141
142
        # save figure
143
        plt.savefig(trend_file)
144