-
Notifications
You must be signed in to change notification settings - Fork 86
/
Copy pathstats.go
132 lines (121 loc) · 5.43 KB
/
stats.go
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
package database
import "time"
// Statistics is a set of counters maintained in the database
type Statistics struct {
Scans int64 `json:"scans"`
Trusts int64 `json:"trusts"`
Analyses int64 `json:"analyses"`
Certificates int64 `json:"certificates"`
PendingScans int64 `json:"pendingScansCount"`
Last24HoursScans []HourlyScansCount `json:"last24HoursScansCount"`
TargetsLast24Hours int64 `json:"targetsLast24Hours"`
DistinctTargetsLast24Hours int64 `json:"distinctTargetsLast24Hours"`
DistinctCertsSeenLast24Hours int64 `json:"distinctCertsSeenLast24Hours"`
DistinctCertsAddedLast24Hours int64 `json:"distinctCertsAddedLast24Hours"`
ScansLast24Hours int64 `json:"scansLast24Hours"`
}
// GetLatestStatisticsFromView retrieves the content of the `statistics` materialized view
// and returns it in a Statistics struct. The freshness of the data is not guaranteed, but if
// the materialized view is older than 5 minutes, an automatic refresh is kicked off *after*
// retrieving the data. In effect, unless you query the stats endpoint constantly, this will
// likely return data several minutes, if not a few hours old.
func (db *DB) GetLatestStatisticsFromView() (stats Statistics, err error) {
var ts time.Time
err = db.QueryRow(`SELECT timestamp, total_scans, total_trust, total_analysis, total_certificates,
count_targets_last24h, count_distinct_targets_last24h, count_certificates_seen_last24h,
count_certificates_added_last24h, count_scans_last24h, pending_scans
FROM statistics`).Scan(&ts, &stats.Scans, &stats.Trusts, &stats.Analyses,
&stats.Certificates, &stats.TargetsLast24Hours, &stats.DistinctTargetsLast24Hours,
&stats.DistinctCertsSeenLast24Hours, &stats.DistinctCertsAddedLast24Hours,
&stats.ScansLast24Hours, &stats.PendingScans)
if ts.Before(time.Now().Add(-(5 * time.Minute))) {
go db.Exec(`REFRESH MATERIALIZED VIEW CONCURRENTLY statistics`)
}
return
}
// CountTableEntries returns the estimated count of scans, trusts relationships, analyses
// and certificates stored in database. The count uses Postgres' own stats counter and is
// not guaranteed to be fully accurate.
func (db *DB) CountTableEntries() (scans, trusts, analyses, certificates int64, err error) {
err = db.QueryRow(`SELECT reltuples::INTEGER FROM pg_class WHERE relname='scans'`).Scan(&scans)
if err != nil {
return
}
err = db.QueryRow(`SELECT reltuples::INTEGER FROM pg_class WHERE relname='trust'`).Scan(&trusts)
if err != nil {
return
}
err = db.QueryRow(`SELECT reltuples::INTEGER FROM pg_class WHERE relname='analysis'`).Scan(&analyses)
if err != nil {
return
}
err = db.QueryRow(`SELECT reltuples::INTEGER FROM pg_class WHERE relname='certificates'`).Scan(&certificates)
if err != nil {
return
}
return
}
// CountPendingScans returns the total number of scans that are pending in the queue
func (db *DB) CountPendingScans() (count int64, err error) {
err = db.QueryRow(`SELECT COUNT(*) FROM scans
WHERE completion_perc = 0
AND attempts < 3 AND ack = false`).Scan(&count)
return
}
// HourlyScansCount represents the number of scans completed over one hour
type HourlyScansCount struct {
Hour time.Time `json:"hour"`
Count int64 `json:"count"`
}
// CountLast24HoursScans returns a list of hourly scans count for the last 24 hours, sorted
// from most recent the oldest
func (db *DB) CountLast24HoursScans() (hourlyStats []HourlyScansCount, err error) {
rows, err := db.Query(`SELECT DATE_TRUNC('hour', "timestamp") AS hour, COUNT(*)
FROM scans
WHERE timestamp > NOW() - INTERVAL '24 hours' AND ack=true AND completion_perc=100
GROUP BY DATE_TRUNC('hour', "timestamp") ORDER BY 1 DESC`)
if err != nil {
return
}
for rows.Next() {
var hsc HourlyScansCount
err = rows.Scan(&hsc.Hour, &hsc.Count)
if err != nil {
return
}
hourlyStats = append(hourlyStats, hsc)
}
return
}
// CountTargetsLast24Hours returns the number of unique targets scanned over the last 24 hours
func (db *DB) CountTargetsLast24Hours() (count, countDistinct int64, err error) {
err = db.QueryRow(`SELECT COUNT(target), COUNT(DISTINCT(target))
FROM scans
WHERE timestamp > NOW() - INTERVAL '24 hours'
AND ack=true
AND completion_perc=100`).Scan(&count, &countDistinct)
return
}
// CountDistinctCertsSeenLast24Hours returns the count of unique certificates seen over the last 24 hours
func (db *DB) CountDistinctCertsSeenLast24Hours() (count int64, err error) {
err = db.QueryRow(`SELECT COUNT(DISTINCT(id))
FROM certificates
WHERE last_seen > NOW() - INTERVAL '24 hours'`).Scan(&count)
return
}
// CountDistinctCertsAddedLast24Hours returns the count of unique certificates added over the last 24 hours
func (db *DB) CountDistinctCertsAddedLast24Hours() (count int64, err error) {
err = db.QueryRow(`SELECT COUNT(DISTINCT(id))
FROM certificates
WHERE first_seen > NOW() - INTERVAL '24 hours'`).Scan(&count)
return
}
// CountScansLast24Hours returns the count of scans over the last 24 hours
func (db *DB) CountScansLast24Hours() (count int64, err error) {
err = db.QueryRow(`SELECT COUNT(id)
FROM scans
WHERE timestamp > NOW() - INTERVAL '24 hours'
AND ack=true
AND completion_perc=100`).Scan(&count)
return
}