174 lines
5.6 KiB
Go
174 lines
5.6 KiB
Go
package probe
|
|
|
|
import (
|
|
"database/sql"
|
|
"strconv"
|
|
)
|
|
|
|
// Span is one chart window as the read side answers it (hub db::Span):
|
|
// the window start in unix seconds, the seconds each returned point
|
|
// covers, and whether the window reaches past the minute tier and is
|
|
// read from the hourly one.
|
|
type Span struct {
|
|
Since int64
|
|
Step int64
|
|
Hourly bool
|
|
}
|
|
|
|
// ComputeSpan reproduces hub api::span(): the point budget defaults to
|
|
// 1440 (a day of minutes) and the caller's points may only lower it,
|
|
// clamped to [60, 1440]; windows longer than DetailDays read hourly
|
|
// rows, so the step is a whole number of minutes — or of hours past the
|
|
// detail window, so no hourly row straddles two points; the window
|
|
// start is aligned down to a step boundary.
|
|
func ComputeSpan(hours int64, points *int64, now int64) Span {
|
|
const samples = 1440
|
|
budget := int64(samples)
|
|
if points != nil {
|
|
budget = *points
|
|
}
|
|
if budget < 60 {
|
|
budget = 60
|
|
}
|
|
if budget > samples {
|
|
budget = samples
|
|
}
|
|
hourly := hours > DetailDays*24
|
|
unit := int64(60)
|
|
if hourly {
|
|
unit = 3600
|
|
}
|
|
// Rounded up, or the budget would not be one.
|
|
q := (hours*3600/unit + budget - 1) / budget
|
|
if q < 1 {
|
|
q = 1
|
|
}
|
|
step := unit * q
|
|
return Span{
|
|
Since: euclidDiv(now-hours*3600, step) * step,
|
|
Step: step,
|
|
Hourly: hourly,
|
|
}
|
|
}
|
|
|
|
func euclidDiv(a, b int64) int64 {
|
|
q := a / b
|
|
if a%b != 0 && (a < 0) != (b < 0) {
|
|
q--
|
|
}
|
|
return q
|
|
}
|
|
|
|
// MetricRow is one thinned history point, in the hub's key order and
|
|
// with the hub's value semantics: means for the gauge columns (byte
|
|
// columns truncated, count columns rounded — the asymmetry is the
|
|
// hub's, kept), maxima for the peak columns, and `minutes` counting
|
|
// the minute rows folded into the point.
|
|
type MetricRow struct {
|
|
Ts int64 `json:"ts"`
|
|
Cpu float64 `json:"cpu"`
|
|
MemUsed int64 `json:"mem_used"`
|
|
DiskUsed int64 `json:"disk_used"`
|
|
NetRx int64 `json:"net_rx"`
|
|
NetTx int64 `json:"net_tx"`
|
|
NetRxMax int64 `json:"net_rx_max"`
|
|
NetTxMax int64 `json:"net_tx_max"`
|
|
CpuMax float64 `json:"cpu_max"`
|
|
Minutes int64 `json:"minutes"`
|
|
SwapUsed int64 `json:"swap_used"`
|
|
Tcp int64 `json:"tcp"`
|
|
Udp int64 `json:"udp"`
|
|
Procs int64 `json:"procs"`
|
|
}
|
|
|
|
// RolledWatermark reads the roll-up watermark (unix seconds of the
|
|
// first hour not yet folded). ok is false when no pass has run yet.
|
|
func (s *Store) RolledWatermark() (int64, bool, error) {
|
|
var raw string
|
|
err := s.db.QueryRow(`SELECT value FROM probe_setting WHERE key = ?`, rolledKey).Scan(&raw)
|
|
if err == sql.ErrNoRows {
|
|
return 0, false, nil
|
|
}
|
|
if err != nil {
|
|
return 0, false, err
|
|
}
|
|
n, perr := strconv.ParseInt(raw, 10, 64)
|
|
if perr != nil {
|
|
return 0, false, nil
|
|
}
|
|
return n, true, nil
|
|
}
|
|
|
|
const metricColumns = `(MIN(ts)/?3)*?3, AVG(cpu), CAST(AVG(mem_used) AS INTEGER),
|
|
CAST(AVG(disk_used) AS INTEGER),
|
|
CAST(AVG(net_rx) AS INTEGER), CAST(AVG(net_tx) AS INTEGER),
|
|
MAX(MAX(net_rx, net_rx_max)), MAX(MAX(net_tx, net_tx_max)),
|
|
MAX(MAX(cpu, cpu_max)), COUNT(*), CAST(AVG(swap_used) AS INTEGER),
|
|
CAST(ROUND(AVG(tcp)) AS INTEGER), CAST(ROUND(AVG(udp)) AS INTEGER),
|
|
CAST(ROUND(AVG(procs)) AS INTEGER)`
|
|
|
|
// MetricHistory answers one agent's history window (hub Db::metrics).
|
|
// Minute windows group minute rows directly. Hourly windows merge the
|
|
// folded hourly tier (up to the watermark) with the not-yet-folded
|
|
// minute rows and the newest week of minute rows, weighted by each
|
|
// row's minute count. Both statements run in one read snapshot so a
|
|
// roll-up committing in between cannot double-count an hour.
|
|
func (s *Store) MetricHistory(agentID int64, sp Span) ([]MetricRow, error) {
|
|
tx, err := s.db.Begin()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
var rows *sql.Rows
|
|
if !sp.Hourly {
|
|
rows, err = tx.Query(
|
|
`SELECT `+metricColumns+`
|
|
FROM probe_metric WHERE agent_id=?1 AND ts>=?2 GROUP BY ts/?3 ORDER BY ts/?3`,
|
|
agentID, sp.Since, sp.Step)
|
|
} else {
|
|
rolled := int64(-1) << 62 // hub: unwrap_or(i64::MIN)
|
|
var raw sql.NullString
|
|
if qerr := tx.QueryRow(`SELECT value FROM probe_setting WHERE key = ?`, rolledKey).Scan(&raw); qerr != nil && qerr != sql.ErrNoRows {
|
|
return nil, qerr
|
|
} else if raw.Valid {
|
|
if n, perr := strconv.ParseInt(raw.String, 10, 64); perr == nil {
|
|
rolled = n
|
|
}
|
|
}
|
|
rows, err = tx.Query(
|
|
`SELECT (MIN(ts)/?3)*?3, SUM(cpu*w)/SUM(w), SUM(mem_used*w)/SUM(w), SUM(disk_used*w)/SUM(w),
|
|
SUM(net_rx*w)/SUM(w), SUM(net_tx*w)/SUM(w), MAX(rx_max), MAX(tx_max), MAX(cpu_top), SUM(w),
|
|
SUM(swap_used*w)/SUM(w), CAST(ROUND(SUM(tcp*w)*1.0/SUM(w)) AS INTEGER),
|
|
CAST(ROUND(SUM(udp*w)*1.0/SUM(w)) AS INTEGER),
|
|
CAST(ROUND(SUM(procs*w)*1.0/SUM(w)) AS INTEGER)
|
|
FROM (SELECT ts, minutes AS w, cpu, mem_used, disk_used, net_rx, net_tx,
|
|
net_rx_max AS rx_max, net_tx_max AS tx_max, cpu_max AS cpu_top,
|
|
swap_used, tcp, udp, procs
|
|
FROM probe_metric_hour WHERE agent_id=?1 AND ts>=?2 AND ts<?4
|
|
UNION ALL
|
|
SELECT ts, 1, cpu, mem_used, disk_used, net_rx, net_tx,
|
|
MAX(net_rx, net_rx_max), MAX(net_tx, net_tx_max), MAX(cpu, cpu_max),
|
|
swap_used, tcp, udp, procs
|
|
FROM probe_metric WHERE agent_id=?1
|
|
AND ts>=MAX(?2, ?4, (SELECT MAX(ts) FROM probe_metric WHERE agent_id=?1) - ?5))
|
|
GROUP BY ts/?3 ORDER BY ts/?3`,
|
|
agentID, sp.Since, sp.Step, rolled, DetailDays*86400)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
out := []MetricRow{}
|
|
for rows.Next() {
|
|
var r MetricRow
|
|
if err := rows.Scan(&r.Ts, &r.Cpu, &r.MemUsed, &r.DiskUsed, &r.NetRx, &r.NetTx,
|
|
&r.NetRxMax, &r.NetTxMax, &r.CpuMax, &r.Minutes, &r.SwapUsed, &r.Tcp, &r.Udp, &r.Procs); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, r)
|
|
}
|
|
return out, rows.Err()
|
|
}
|