blob: a0aa7d41d3953d24a5f1c1dac17d1f55bf23f719 [file] [log] [blame]
Allen Li24bf8182017-03-02 16:41:20 -08001# Copyright 2017 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Git repo metrics."""
6
7from __future__ import absolute_import
8from __future__ import print_function
Allen Li24bf8182017-03-02 16:41:20 -08009
Allen Li8934e042017-06-21 15:54:42 -070010import collections
Allen Li24bf8182017-03-02 16:41:20 -080011import os
12import subprocess
13
Allen Li24bf8182017-03-02 16:41:20 -080014from chromite.lib import cros_logging as logging
15from infra_libs import ts_mon
16
17logger = logging.getLogger(__name__)
18
19
Allen Li8934e042017-06-21 15:54:42 -070020_Stats = collections.namedtuple('_Stats', 'added,deleted,path')
21
22
Allen Li24bf8182017-03-02 16:41:20 -080023class _GitRepo(object):
Paul Hobbse46a42b2017-03-21 14:04:13 -070024 """Helper class for running git commands."""
Allen Li24bf8182017-03-02 16:41:20 -080025
26 def __init__(self, gitdir):
27 self._gitdir = gitdir
28
29 def _get_git_command(self):
30 return ['git', '--git-dir', self._gitdir]
31
32 def _check_output(self, args, **kwargs):
33 return subprocess.check_output(
34 self._get_git_command() + list(args), **kwargs)
35
36 def get_commit_hash(self):
Allen Lid2333982017-04-04 17:03:32 -070037 """Return commit hash string."""
Allen Li24bf8182017-03-02 16:41:20 -080038 return self._check_output(['rev-parse', 'HEAD']).strip()
39
40 def get_commit_time(self):
Allen Lid2333982017-04-04 17:03:32 -070041 """Return commit time as UNIX timestamp int."""
Allen Li24bf8182017-03-02 16:41:20 -080042 return int(self._check_output(['show', '-s', '--format=%ct', 'HEAD'])
43 .strip())
44
Allen Li8934e042017-06-21 15:54:42 -070045 def get_unstaged_changes(self):
46 """Return number of unstaged changes as (added, deleted)."""
47 added, deleted = 0, 0
48 # output looks like '1\t2\tfoo\n3\t4\tbar\n'
49 output = self._check_output(['diff-index', '--numstat', 'HEAD'])
50 stats_strings = (line.split() for line in output.splitlines())
51 stats_iter = (_Stats(int(added), int(deleted), path)
52 for added, deleted, path in stats_strings)
53 for stats in stats_iter:
54 added += stats.added
55 deleted += stats.deleted
56 return added, deleted
57
Allen Li24bf8182017-03-02 16:41:20 -080058
59class _GitMetricCollector(object):
Allen Libdb9f042017-04-10 13:25:47 -070060 """Class for collecting metrics about a git repository.
61
62 The constructor takes the arguments: `gitdir`, `metric_path`.
63 `gitdir` is the path to the Git directory to collect metrics for and
64 may start with a tilde (expanded to a user's home directory).
65 `metric_path` is the Monarch metric path to report to.
66 """
Allen Li24bf8182017-03-02 16:41:20 -080067
68 _commit_hash_metric = ts_mon.StringMetric(
69 'git/hash',
70 description='Current Git commit hash.')
71
Allen Lid523d962017-04-04 16:48:36 -070072 _timestamp_metric = ts_mon.GaugeMetric(
73 'git/timestamp',
Allen Li24bf8182017-03-02 16:41:20 -080074 description='Current Git commit time as seconds since Unix Epoch.')
75
Allen Li8934e042017-06-21 15:54:42 -070076 _unstaged_changes_metric = ts_mon.GaugeMetric(
77 'git/unstaged_changes',
78 description='Unstaged Git changes.')
79
Allen Li24bf8182017-03-02 16:41:20 -080080 def __init__(self, gitdir, metric_path):
81 self._gitdir = gitdir
Allen Libdb9f042017-04-10 13:25:47 -070082 self._gitrepo = _GitRepo(os.path.expanduser(gitdir))
Allen Li24bf8182017-03-02 16:41:20 -080083 self._fields = {'repo': gitdir}
84 self._metric_path = metric_path
85
86 def collect(self):
87 """Collect metrics."""
88 try:
89 self._collect_commit_hash_metric()
Allen Lid523d962017-04-04 16:48:36 -070090 self._collect_timestamp_metric()
Allen Li8934e042017-06-21 15:54:42 -070091 self._collect_unstaged_changes_metric()
Allen Li24bf8182017-03-02 16:41:20 -080092 except subprocess.CalledProcessError as e:
Allen Li867d4582017-05-24 18:00:43 -070093 logger.warning(u'Error collecting git metrics for %s: %s',
Allen Li24bf8182017-03-02 16:41:20 -080094 self._gitdir, e)
95
96 def _collect_commit_hash_metric(self):
97 commit_hash = self._gitrepo.get_commit_hash()
Allen Li867d4582017-05-24 18:00:43 -070098 logger.debug(u'Collecting Git hash %r for %r', commit_hash, self._gitdir)
Allen Li24bf8182017-03-02 16:41:20 -080099 self._commit_hash_metric.set(commit_hash, self._fields)
100
Allen Lid523d962017-04-04 16:48:36 -0700101 def _collect_timestamp_metric(self):
Allen Li24bf8182017-03-02 16:41:20 -0800102 commit_time = self._gitrepo.get_commit_time()
Allen Li867d4582017-05-24 18:00:43 -0700103 logger.debug(u'Collecting Git timestamp %r for %r',
Allen Li24bf8182017-03-02 16:41:20 -0800104 commit_time, self._gitdir)
Allen Lid523d962017-04-04 16:48:36 -0700105 self._timestamp_metric.set(commit_time, self._fields)
Allen Li24bf8182017-03-02 16:41:20 -0800106
Allen Li8934e042017-06-21 15:54:42 -0700107 def _collect_unstaged_changes_metric(self):
108 added, deleted = self._gitrepo.get_commit_time()
109 self._timestamp_metric.set(
110 added, fields=dict(change_type='added', **self._fields))
111 self._timestamp_metric.set(
112 deleted, fields=dict(change_type='deleted', **self._fields))
113
Allen Li24bf8182017-03-02 16:41:20 -0800114
Allen Libdb9f042017-04-10 13:25:47 -0700115_CHROMIUMOS_DIR = '~chromeos-test/chromiumos/'
Allen Li24bf8182017-03-02 16:41:20 -0800116
117_repo_collectors = (
Allen Lia02d34a2017-04-04 17:13:46 -0700118 # TODO(ayatane): We cannot access chromeos-admin because we are
119 # running as non-root.
120 _GitMetricCollector(gitdir='/root/chromeos-admin/.git',
121 metric_path='chromeos-admin'),
122 _GitMetricCollector(gitdir=_CHROMIUMOS_DIR + 'chromite/.git',
123 metric_path='chromite'),
124 _GitMetricCollector(gitdir='/usr/local/autotest/.git',
125 metric_path='installed_autotest'),
Allen Li24bf8182017-03-02 16:41:20 -0800126)
127
128
129def collect_git_metrics():
130 """Collect metrics for Git repository state."""
131 for collector in _repo_collectors:
132 collector.collect()