add per call stats field as histogram (#528)

* add per call stats field as histogram

this will add a histogram of up to 240 data points of call data, produced
every second, stored at the end of a call invocation in the db. the same
metrics are also still shipped to prometheus (prometheus has the
not-potentially-reduced version). for the API reference, see the updates to
the swagger spec, this is just added onto the get call endpoint.

this does not add any extra db calls and the field for stats in call is a json
blob, which is easily modified to add / omit future fields. this is just
tacked on to the call we're making to InsertCall, and expect this to add very
little overhead; we are bounding the set to be relatively small, planning to
clean out the db of calls periodically, functions will generally be short, and
the same code used at a previous firm did not cause a notable db size increase
with production workload that is worse, wrt histogram size (I checked). the
code changes are really small aside from changing to strfmt.DateTime,
adding a migration and implementing sql.Valuer; needed to slightly modify the
swap function so that we can safely read `call.Stats` field to upload at end.

with the full histogram in hand, we can compute max/min/average/median/growth
rate/bernoulli distributions/whatever very easily in a UI or tooling. in
particular, this data is easily chartable [for a UI], which is beneficial.

* adds swagger spec of api update to calls endpoint
* adds migration for call.stats field
* adds call.stats field to sql queries
* change swapping of hot logger to exec, so we know that call.Stats is no
longer being modified after `exec` [in call.End]
* throws out docker stats between function invocations in hot functions (no
call to store them on, we could change this later for debug; they're in prom)
* tested in tests and API

closes #19

* add format of ints to swag
This commit is contained in:
Reed Allman
2017-11-27 08:52:53 -06:00
committed by GitHub
parent d8c41aac9d
commit c9198b8525
12 changed files with 202 additions and 32 deletions

View File

@@ -0,0 +1 @@
ALTER TABLE calls DROP COLUMN stats;

View File

@@ -0,0 +1 @@
ALTER TABLE calls ADD stats text;

View File

@@ -1,6 +1,6 @@
package migrations
//go:generate go-bindata -ignore migrations.go -ignore index.go -o migrations.go -pkg migrations .
//go:generate go-bindata -ignore README.md -ignore migrations.go -ignore index.go -o migrations.go -pkg migrations .
// migrations are generated from this cwd with go generate.
// install https://github.com/jteeuwen/go-bindata for go generate

View File

@@ -2,6 +2,8 @@
// sources:
// 1_add_route_created_at.down.sql
// 1_add_route_created_at.up.sql
// 2_add_call_stats.down.sql
// 2_add_call_stats.up.sql
// DO NOT EDIT!
package migrations
@@ -84,7 +86,7 @@ func _1_add_route_created_atDownSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1_add_route_created_at.down.sql", size: 43, mode: os.FileMode(420), modTime: time.Unix(1508386173, 0)}
info := bindataFileInfo{name: "1_add_route_created_at.down.sql", size: 43, mode: os.FileMode(420), modTime: time.Unix(1510786558, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -104,7 +106,47 @@ func _1_add_route_created_atUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1_add_route_created_at.up.sql", size: 40, mode: os.FileMode(420), modTime: time.Unix(1508360377, 0)}
info := bindataFileInfo{name: "1_add_route_created_at.up.sql", size: 40, mode: os.FileMode(420), modTime: time.Unix(1510786558, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __2_add_call_statsDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\x4e\xcc\xc9\x29\x56\x70\x09\xf2\x0f\x50\x70\xf6\xf7\x09\xf5\xf5\x53\x28\x2e\x49\x2c\x29\xb6\xe6\x02\x04\x00\x00\xff\xff\xd3\x09\xeb\x22\x25\x00\x00\x00")
func _2_add_call_statsDownSqlBytes() ([]byte, error) {
return bindataRead(
__2_add_call_statsDownSql,
"2_add_call_stats.down.sql",
)
}
func _2_add_call_statsDownSql() (*asset, error) {
bytes, err := _2_add_call_statsDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "2_add_call_stats.down.sql", size: 37, mode: os.FileMode(420), modTime: time.Unix(1511225799, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __2_add_call_statsUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\x4e\xcc\xc9\x29\x56\x70\x74\x71\x51\x28\x2e\x49\x2c\x29\x56\x28\x49\xad\x28\xb1\xe6\x02\x04\x00\x00\xff\xff\x29\xde\x11\xe8\x22\x00\x00\x00")
func _2_add_call_statsUpSqlBytes() ([]byte, error) {
return bindataRead(
__2_add_call_statsUpSql,
"2_add_call_stats.up.sql",
)
}
func _2_add_call_statsUpSql() (*asset, error) {
bytes, err := _2_add_call_statsUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "2_add_call_stats.up.sql", size: 34, mode: os.FileMode(420), modTime: time.Unix(1511225651, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -163,6 +205,8 @@ func AssetNames() []string {
var _bindata = map[string]func() (*asset, error){
"1_add_route_created_at.down.sql": _1_add_route_created_atDownSql,
"1_add_route_created_at.up.sql": _1_add_route_created_atUpSql,
"2_add_call_stats.down.sql": _2_add_call_statsDownSql,
"2_add_call_stats.up.sql": _2_add_call_statsUpSql,
}
// AssetDir returns the file names below a certain
@@ -208,6 +252,8 @@ type bintree struct {
var _bintree = &bintree{nil, map[string]*bintree{
"1_add_route_created_at.down.sql": &bintree{_1_add_route_created_atDownSql, map[string]*bintree{}},
"1_add_route_created_at.up.sql": &bintree{_1_add_route_created_atUpSql, map[string]*bintree{}},
"2_add_call_stats.down.sql": &bintree{_2_add_call_statsDownSql, map[string]*bintree{}},
"2_add_call_stats.up.sql": &bintree{_2_add_call_statsUpSql, map[string]*bintree{}},
}}
// RestoreAsset restores an asset under the given directory

View File

@@ -65,6 +65,7 @@ var tables = [...]string{`CREATE TABLE IF NOT EXISTS routes (
id varchar(256) NOT NULL,
app_name varchar(256) NOT NULL,
path varchar(256) NOT NULL,
stats text,
PRIMARY KEY (id)
);`,
@@ -77,7 +78,7 @@ var tables = [...]string{`CREATE TABLE IF NOT EXISTS routes (
const (
routeSelector = `SELECT app_name, path, image, format, memory, type, timeout, idle_timeout, headers, config, created_at FROM routes`
callSelector = `SELECT id, created_at, started_at, completed_at, status, app_name, path FROM calls`
callSelector = `SELECT id, created_at, started_at, completed_at, status, app_name, path, stats FROM calls`
)
type sqlStore struct {
@@ -585,7 +586,8 @@ func (ds *sqlStore) InsertCall(ctx context.Context, call *models.Call) error {
completed_at,
status,
app_name,
path
path,
stats
)
VALUES (
:id,
@@ -594,7 +596,8 @@ func (ds *sqlStore) InsertCall(ctx context.Context, call *models.Call) error {
:completed_at,
:status,
:app_name,
:path
:path,
:stats
);`)
_, err := ds.db.NamedExecContext(ctx, query, call)