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
|
package stats
import (
"bufio"
"context"
"io"
"strconv"
"strings"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
"gitlab.com/gitlab-org/gitaly/v14/internal/git"
"gitlab.com/gitlab-org/gitaly/v14/internal/git/repository"
)
// LogObjectsInfo read statistics of the git repo objects
// and logs it under 'count-objects' key as structured entry.
func LogObjectsInfo(ctx context.Context, gitCmdFactory git.CommandFactory, repo repository.GitRepo) {
logger := ctxlogrus.Extract(ctx)
cmd, err := gitCmdFactory.New(ctx, repo, git.SubCmd{
Name: "count-objects",
Flags: []git.Option{git.Flag{Name: "--verbose"}},
})
if err != nil {
logger.WithError(err).Warn("failed on bootstrapping to gather object statistic")
return
}
stats, err := readObjectInfoStatistic(cmd)
if err != nil {
logger.WithError(err).Warn("failed on reading to gather object statistic")
}
if err := cmd.Wait(); err != nil {
logger.WithError(err).Warn("failed on waiting to gather object statistic")
return
}
if len(stats) > 0 {
logger.WithField("count_objects", stats).Info("git repo statistic")
}
}
/* readObjectInfoStatistic parses output of 'git count-objects -v' command and represents it as dictionary
current supported format is:
count: 12
packs: 2
size-garbage: 934
alternate: /some/path/to/.git/objects
alternate: "/some/other path/to/.git/objects"
will result in:
{
"count": 12,
"packs": 2,
"size-garbage": 934,
"alternate": ["/some/path/to/.git/objects", "/some/other path/to/.git/objects"]
}
*/
func readObjectInfoStatistic(reader io.Reader) (map[string]interface{}, error) {
stats := map[string]interface{}{}
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
parts := strings.SplitN(line, ": ", 2)
if len(parts) != 2 {
continue
}
// one of: count, size, in-pack, packs, size-pack, prune-packable, garbage, size-garbage, alternate (repeatable)
key := parts[0]
rawVal := strings.TrimPrefix(parts[1], ": ")
switch key {
case "alternate":
addMultiString(stats, key, rawVal)
default:
addInt(stats, key, rawVal)
}
}
return stats, scanner.Err()
}
func addMultiString(stats map[string]interface{}, key, rawVal string) {
val := strings.Trim(rawVal, "\" \t\n")
statVal, found := stats[key]
if !found {
stats[key] = val
return
}
statAggr, ok := statVal.([]string) // 'alternate' is only repeatable key and it is a string type
if ok {
statAggr = append(statAggr, val)
} else {
delete(stats, key) // remove single string value of 'alternate' to replace it with slice
statAggr = []string{statVal.(string), val}
}
stats[key] = statAggr
}
func addInt(stats map[string]interface{}, key, rawVal string) {
val, err := strconv.ParseInt(rawVal, 10, 64)
if err != nil {
return
}
stats[key] = val
}
|