blob: f75e396882153829101a6c6d10cc57aa0be27b6d [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
Allen Li24bf8182017-03-02 16:41:20 -08008
9import os
10import subprocess
11
Allen Li24bf8182017-03-02 16:41:20 -080012from chromite.lib import cros_logging as logging
Allen Lia9c6e802017-07-11 15:42:47 -070013from chromite.lib import metrics
Allen Li24bf8182017-03-02 16:41:20 -080014
15logger = logging.getLogger(__name__)
16
17
18class _GitRepo(object):
Paul Hobbse46a42b2017-03-21 14:04:13 -070019 """Helper class for running git commands."""
Allen Li24bf8182017-03-02 16:41:20 -080020
21 def __init__(self, gitdir):
22 self._gitdir = gitdir
23
24 def _get_git_command(self):
25 return ['git', '--git-dir', self._gitdir]
26
27 def _check_output(self, args, **kwargs):
28 return subprocess.check_output(
Mike Frysinger6e22a4d2020-03-10 13:53:32 -040029 self._get_git_command() + list(args), **kwargs).decode('utf-8')
Allen Li24bf8182017-03-02 16:41:20 -080030
31 def get_commit_hash(self):
Allen Lid2333982017-04-04 17:03:32 -070032 """Return commit hash string."""
Allen Li24bf8182017-03-02 16:41:20 -080033 return self._check_output(['rev-parse', 'HEAD']).strip()
34
35 def get_commit_time(self):
Allen Lid2333982017-04-04 17:03:32 -070036 """Return commit time as UNIX timestamp int."""
Allen Li24bf8182017-03-02 16:41:20 -080037 return int(self._check_output(['show', '-s', '--format=%ct', 'HEAD'])
38 .strip())
39
Allen Li8934e042017-06-21 15:54:42 -070040 def get_unstaged_changes(self):
41 """Return number of unstaged changes as (added, deleted)."""
Allen Li5956da62017-06-27 12:38:49 -070042 added_total, deleted_total = 0, 0
43 # output looks like:
44 # '1\t2\tfoo\n3\t4\tbar\n'
45 # '-\t-\tbinary_file\n'
Allen Li8934e042017-06-21 15:54:42 -070046 output = self._check_output(['diff-index', '--numstat', 'HEAD'])
47 stats_strings = (line.split() for line in output.splitlines())
Allen Li5956da62017-06-27 12:38:49 -070048 for added, deleted, _path in stats_strings:
49 if added != '-':
50 added_total += int(added)
51 if deleted != '-':
52 deleted_total += int(deleted)
53 return added_total, deleted_total
Allen Li8934e042017-06-21 15:54:42 -070054
Allen Li24bf8182017-03-02 16:41:20 -080055
56class _GitMetricCollector(object):
Allen Libdb9f042017-04-10 13:25:47 -070057 """Class for collecting metrics about a git repository.
58
59 The constructor takes the arguments: `gitdir`, `metric_path`.
60 `gitdir` is the path to the Git directory to collect metrics for and
61 may start with a tilde (expanded to a user's home directory).
62 `metric_path` is the Monarch metric path to report to.
63 """
Allen Li24bf8182017-03-02 16:41:20 -080064
Allen Lia9c6e802017-07-11 15:42:47 -070065 _commit_hash_metric = metrics.StringMetric(
Allen Li24bf8182017-03-02 16:41:20 -080066 'git/hash',
67 description='Current Git commit hash.')
68
Allen Lia9c6e802017-07-11 15:42:47 -070069 _timestamp_metric = metrics.GaugeMetric(
Allen Lid523d962017-04-04 16:48:36 -070070 'git/timestamp',
Allen Li24bf8182017-03-02 16:41:20 -080071 description='Current Git commit time as seconds since Unix Epoch.')
72
Allen Lia9c6e802017-07-11 15:42:47 -070073 _unstaged_changes_metric = metrics.GaugeMetric(
Allen Li8934e042017-06-21 15:54:42 -070074 'git/unstaged_changes',
75 description='Unstaged Git changes.')
76
Allen Li24bf8182017-03-02 16:41:20 -080077 def __init__(self, gitdir, metric_path):
78 self._gitdir = gitdir
Allen Libdb9f042017-04-10 13:25:47 -070079 self._gitrepo = _GitRepo(os.path.expanduser(gitdir))
Allen Li24bf8182017-03-02 16:41:20 -080080 self._fields = {'repo': gitdir}
81 self._metric_path = metric_path
82
83 def collect(self):
84 """Collect metrics."""
85 try:
86 self._collect_commit_hash_metric()
Allen Lid523d962017-04-04 16:48:36 -070087 self._collect_timestamp_metric()
Allen Li8934e042017-06-21 15:54:42 -070088 self._collect_unstaged_changes_metric()
Allen Li24bf8182017-03-02 16:41:20 -080089 except subprocess.CalledProcessError as e:
Allen Li867d4582017-05-24 18:00:43 -070090 logger.warning(u'Error collecting git metrics for %s: %s',
Allen Li24bf8182017-03-02 16:41:20 -080091 self._gitdir, e)
92
93 def _collect_commit_hash_metric(self):
94 commit_hash = self._gitrepo.get_commit_hash()
Allen Li867d4582017-05-24 18:00:43 -070095 logger.debug(u'Collecting Git hash %r for %r', commit_hash, self._gitdir)
Allen Li24bf8182017-03-02 16:41:20 -080096 self._commit_hash_metric.set(commit_hash, self._fields)
97
Allen Lid523d962017-04-04 16:48:36 -070098 def _collect_timestamp_metric(self):
Allen Li24bf8182017-03-02 16:41:20 -080099 commit_time = self._gitrepo.get_commit_time()
Allen Li867d4582017-05-24 18:00:43 -0700100 logger.debug(u'Collecting Git timestamp %r for %r',
Allen Li24bf8182017-03-02 16:41:20 -0800101 commit_time, self._gitdir)
Allen Lid523d962017-04-04 16:48:36 -0700102 self._timestamp_metric.set(commit_time, self._fields)
Allen Li24bf8182017-03-02 16:41:20 -0800103
Allen Li8934e042017-06-21 15:54:42 -0700104 def _collect_unstaged_changes_metric(self):
Allen Lic8acdec2017-06-26 15:09:41 -0700105 added, deleted = self._gitrepo.get_unstaged_changes()
Allen Li36b8a8b2017-06-28 14:47:16 -0700106 self._unstaged_changes_metric.set(
Allen Li8934e042017-06-21 15:54:42 -0700107 added, fields=dict(change_type='added', **self._fields))
Allen Li36b8a8b2017-06-28 14:47:16 -0700108 self._unstaged_changes_metric.set(
Allen Li8934e042017-06-21 15:54:42 -0700109 deleted, fields=dict(change_type='deleted', **self._fields))
110
Allen Li24bf8182017-03-02 16:41:20 -0800111
Allen Libdb9f042017-04-10 13:25:47 -0700112_CHROMIUMOS_DIR = '~chromeos-test/chromiumos/'
Allen Li24bf8182017-03-02 16:41:20 -0800113
114_repo_collectors = (
Allen Lia02d34a2017-04-04 17:13:46 -0700115 # TODO(ayatane): We cannot access chromeos-admin because we are
116 # running as non-root.
117 _GitMetricCollector(gitdir='/root/chromeos-admin/.git',
118 metric_path='chromeos-admin'),
119 _GitMetricCollector(gitdir=_CHROMIUMOS_DIR + 'chromite/.git',
120 metric_path='chromite'),
121 _GitMetricCollector(gitdir='/usr/local/autotest/.git',
122 metric_path='installed_autotest'),
Allen Li24bf8182017-03-02 16:41:20 -0800123)
124
125
126def collect_git_metrics():
127 """Collect metrics for Git repository state."""
128 for collector in _repo_collectors:
129 collector.collect()