Example #1
0
/*
spark draws an svg spark line to b.  Assumes f.loadPK has been called first.
*/
func dataCompletenessSpark(siteID, typeID string, b *bytes.Buffer) *weft.Result {
	var p ts.Plot

	p.SetXAxis(time.Now().UTC().Add(time.Hour*-12), time.Now().UTC())

	var err error
	var rows *sql.Rows
	var expected int
	var typePK int

	if err = dbR.QueryRow(`SELECT typePK, expected FROM data.completeness_type WHERE typeID = $1`,
		typeID).Scan(&typePK, &expected); err != nil {
		if err == sql.ErrNoRows {
			return &weft.NotFound
		}
		return weft.InternalServerError(err)
	}

	expectedf := float64(expected)

	if rows, err = dbR.Query(`SELECT date_trunc('hour',time) as t, sum(count) FROM data.completeness
		WHERE sitePK = (SELECT sitePK FROM data.site WHERE siteID = $1)
		AND typePK = $2
		AND time > now() - interval '28 days'
		GROUP BY date_trunc('hour',time)
		ORDER BY t ASC`,
		siteID, typePK); err != nil {
		return weft.InternalServerError(err)
	}

	defer rows.Close()

	var pts []ts.Point

	for rows.Next() {
		var pt ts.Point
		var v int
		if err = rows.Scan(&pt.DateTime, &v); err != nil {
			return weft.InternalServerError(err)
		}
		// No need to scale spark data for display.
		pt.Value = float64(v) / expectedf
		pts = append(pts, pt)
	}

	if len(pts) > 0 {
		p.AddSeries(ts.Series{Colour: "deepskyblue", Points: pts})
	}

	if err = ts.SparkLine.Draw(p, b); err != nil {
		return weft.InternalServerError(err)
	}

	return &weft.StatusOK
}
Example #2
0
/*
spark draws an svg spark line to b.  Assumes f.loadPK has been called first.
*/
func dataLatencySpark(siteID, typeID string, b *bytes.Buffer) *weft.Result {
	var p ts.Plot

	p.SetXAxis(time.Now().UTC().Add(time.Hour*-12), time.Now().UTC())

	var err error
	var rows *sql.Rows

	if rows, err = dbR.Query(`SELECT date_trunc('hour', time) + extract(minute from time)::int / 5 * interval '5 min' as t,
		 avg(mean) FROM data.latency
		 WHERE sitePK = (SELECT sitePK FROM data.site WHERE siteID = $1)
		 AND typePK = (SELECT typePK FROM data.type WHERE typeID = $2)
		 AND time > now() - interval '12 hours'
		 GROUP BY date_trunc('hour', time) + extract(minute from time)::int / 5 * interval '5 min'
		 ORDER BY t ASC`,
		siteID, typeID); err != nil {
		return weft.InternalServerError(err)
	}

	defer rows.Close()

	var pts []ts.Point

	for rows.Next() {
		var pt ts.Point
		if err = rows.Scan(&pt.DateTime, &pt.Value); err != nil {
			return weft.InternalServerError(err)
		}
		// No need to scale spark data for display.
		pts = append(pts, pt)
	}
	rows.Close()

	p.AddSeries(ts.Series{Colour: internal.Colour(int(internal.Mean)), Points: pts})

	if err = ts.SparkLine.Draw(p, b); err != nil {
		return weft.InternalServerError(err)
	}

	return &weft.StatusOK
}
Example #3
0
/*
plot draws an svg plot to b.  Assumes f.loadPK has been called first.
*/
func dataCompletenessPlot(siteID, typeID, resolution string, plotter ts.SVGPlot, b *bytes.Buffer) *weft.Result {
	var err error
	// we need the sitePK often so read it once.
	var sitePK int
	if err = dbR.QueryRow(`SELECT sitePK FROM data.site WHERE siteID = $1`,
		siteID).Scan(&sitePK); err != nil {
		if err == sql.ErrNoRows {
			return &weft.NotFound
		}
		return weft.InternalServerError(err)
	}

	var typePK int
	var expected int

	if err = dbR.QueryRow(`SELECT typePK, expected FROM data.completeness_type WHERE typeID = $1`,
		typeID).Scan(&typePK, &expected); err != nil {
		if err == sql.ErrNoRows {
			return &weft.NotFound
		}
		return weft.InternalServerError(err)
	}

	expectedf := float64(expected)
	var p ts.Plot

	var tags []string
	var rows *sql.Rows

	if rows, err = dbR.Query(`SELECT tag FROM data.completeness_tag JOIN mtr.tag USING (tagpk) WHERE
		sitePK = $1 AND typePK = $2
		ORDER BY tag asc`,
		sitePK, typePK); err != nil {

		return weft.InternalServerError(err)
	}

	defer rows.Close()

	for rows.Next() {
		var s string
		if err = rows.Scan(&s); err != nil {
			return weft.InternalServerError(err)
		}
		tags = append(tags, s)
	}
	rows.Close()

	p.SetSubTitle("Tags: " + strings.Join(tags, ","))
	p.SetTitle(fmt.Sprintf("Site: %s - %s", siteID, strings.Title(typeID)))
	p.SetUnit("completeness")

	switch resolution {
	case "five_minutes":
		p.SetXAxis(time.Now().UTC().Add(time.Hour*-24*2), time.Now().UTC())
		p.SetXLabel("48 hours")

		expectedf /= 288
		rows, err = dbR.Query(`SELECT date_trunc('hour', time) + extract(minute from time)::int / 5 * interval '5 min' as t,
		 sum(count) FROM data.completeness WHERE
		sitePK = $1 AND typePK = $2
		AND time > now() - interval '2 days'
		GROUP BY date_trunc('hour', time) + extract(minute from time)::int / 5 * interval '5 min'
		ORDER BY t ASC`,
			sitePK, typePK)
	case "hour":
		p.SetXAxis(time.Now().UTC().Add(time.Hour*-24*28), time.Now().UTC())
		p.SetXLabel("4 weeks")

		expectedf /= 24
		rows, err = dbR.Query(`SELECT date_trunc('`+resolution+`',time) as t, sum(count) FROM data.completeness WHERE
		sitePK = $1 AND typePK = $2
		AND time > now() - interval '28 days'
		GROUP BY date_trunc('`+resolution+`',time)
		ORDER BY t ASC`,
			sitePK, typePK)
	case "twelve_hours":
		p.SetXAxis(time.Now().UTC().Add(time.Hour*-24*28), time.Now().UTC())
		p.SetXLabel("4 weeks")

		expectedf /= 2
		rows, err = dbR.Query(`SELECT date_trunc('hour', time) + extract(hour from time)::int / 12 * interval '12 hour' as t, sum(count) FROM data.completeness WHERE
		sitePK = $1 AND typePK = $2
		AND time > now() - interval '28 days'
		GROUP BY date_trunc('hour', time) + extract(hour from time)::int / 12 * interval '12 hour'
		ORDER BY t ASC`,
			sitePK, typePK)
	default:
		return weft.BadRequest("invalid resolution")
	}
	if err != nil {
		return weft.InternalServerError(err)
	}

	var pts []ts.Point

	for rows.Next() {
		var pt ts.Point
		var v int
		if err = rows.Scan(&pt.DateTime, &v); err != nil {
			return weft.InternalServerError(err)
		}

		pt.Value = float64(v) / expectedf
		pts = append(pts, pt)
	}

	// Add the latest value to the plot.
	var pt ts.Point

	if err = dbR.QueryRow(`SELECT time, count FROM data.completeness WHERE
			sitePK = $1 AND typePK = $2
			ORDER BY time DESC
			LIMIT 1`,
		sitePK, typePK).Scan(&pt.DateTime, &pt.Value); err != nil {
		// Note: We keep rendering the plot even there's no data.
		if err != sql.ErrNoRows {
			return weft.InternalServerError(err)
		}
		pt.Value = pt.Value / expectedf

		pts = append(pts, pt)
		p.SetLatest(pt, "deepskyblue")
	}

	p.AddSeries(ts.Series{Colour: "deepskyblue", Points: pts})

	if err = plotter.Draw(p, b); err != nil {
		return weft.InternalServerError(err)
	}

	return &weft.StatusOK
}
Example #4
0
/*
plot draws an svg plot to b.
Valid values for resolution are 'minute', 'five_minutes', 'hour'.
*/
func (f fieldMetric) plot(deviceID, typeID, resolution string, plotter ts.SVGPlot, b *bytes.Buffer) *weft.Result {
	// we need the devicePK often so read it once.
	var devicePK int
	if err := dbR.QueryRow(`SELECT devicePK FROM field.device WHERE deviceID = $1`,
		deviceID).Scan(&devicePK); err != nil {
		if err == sql.ErrNoRows {
			return &weft.NotFound
		}
		return weft.InternalServerError(err)
	}

	var typePK int
	var scale float64
	var display string

	if err := dbR.QueryRow(`SELECT typePK, scale, display FROM field.type WHERE typeID = $1`,
		typeID).Scan(&typePK, &scale, &display); err != nil {
		if err == sql.ErrNoRows {
			return &weft.NotFound
		}
		return weft.InternalServerError(err)
	}

	var p ts.Plot

	p.SetUnit(display)

	var rows *sql.Rows
	var err error
	var lower, upper int

	if err := dbR.QueryRow(`SELECT lower,upper FROM field.threshold
		WHERE devicePK = $1 AND typePK = $2`,
		devicePK, typePK).Scan(&lower, &upper); err != nil && err != sql.ErrNoRows {
		return weft.InternalServerError(err)
	}

	if !(lower == 0 && upper == 0) {
		p.SetThreshold(float64(lower)*scale, float64(upper)*scale)
	}

	var tags []string

	if rows, err = dbR.Query(`SELECT tag FROM field.metric_tag JOIN mtr.tag USING (tagpk) WHERE
		devicePK = $1 AND typePK = $2
		ORDER BY tag asc`,
		devicePK, typePK); err != nil {
		return weft.InternalServerError(err)
	}

	defer rows.Close()

	for rows.Next() {
		var s string
		if err = rows.Scan(&s); err != nil {
			return weft.InternalServerError(err)
		}
		tags = append(tags, s)
	}

	rows.Close()

	p.SetSubTitle("Tags: " + strings.Join(tags, ","))

	var mod string
	// TODO move into first select for devicePK
	if err = dbR.QueryRow(`SELECT modelid FROM field.device JOIN field.model using (modelpk)
		WHERE devicePK = $1`,
		devicePK).Scan(&mod); err != nil && err != sql.ErrNoRows {
		return weft.InternalServerError(err)
	}

	p.SetTitle(fmt.Sprintf("Device: %s, Model: %s, Metric: %s", deviceID, mod, strings.Title(typeID)))

	switch resolution {
	case "minute":
		p.SetXAxis(time.Now().UTC().Add(time.Hour*-12), time.Now().UTC())
		p.SetXLabel("12 hours")
	case "five_minutes":
		p.SetXAxis(time.Now().UTC().Add(time.Hour*-24*2), time.Now().UTC())
		p.SetXLabel("48 hours")
	case "hour":
		p.SetXAxis(time.Now().UTC().Add(time.Hour*-24*28), time.Now().UTC())
		p.SetXLabel("4 weeks")
	default:
		return weft.BadRequest("invalid resolution")
	}

	var timeRange []time.Time
	if timeRange, err = defaultTimeRange(resolution); err != nil {
		return weft.InternalServerError(err)
	}

	rows, err = queryMetricRows(devicePK, typePK, resolution, timeRange)
	if err != nil {
		return weft.InternalServerError(err)
	}

	defer rows.Close()

	var pts []ts.Point

	for rows.Next() {
		var pt ts.Point
		if err = rows.Scan(&pt.DateTime, &pt.Value); err != nil {
			return weft.InternalServerError(err)
		}
		pt.Value = pt.Value * scale
		pts = append(pts, pt)
	}
	rows.Close()

	// Add the latest value to the plot - this may be different to the average at minute or hour resolution.
	var pt ts.Point

	if err = dbR.QueryRow(`SELECT time, value FROM field.metric WHERE
			devicePK = $1 AND typePK = $2
			ORDER BY time DESC
			LIMIT 1`,
		devicePK, typePK).Scan(&pt.DateTime, &pt.Value); err != nil {
		return weft.InternalServerError(err)
	}

	pt.Value = pt.Value * scale

	pts = append(pts, pt)
	p.SetLatest(pt, "deepskyblue")

	p.AddSeries(ts.Series{Colour: "deepskyblue", Points: pts})

	if err = plotter.Draw(p, b); err != nil {
		return weft.InternalServerError(err)
	}

	return &weft.StatusOK
}
Example #5
0
func (a appMetric) loadAppMetrics(applicationID, resolution string, typeID internal.ID, timeRange []time.Time, p *ts.Plot) *weft.Result {
	var err error

	var rows *sql.Rows

	rows, err = dbR.Query(`SELECT COUNT(*)
		FROM app.type, app.application
		WHERE applicationID = $1
		AND typePK = $2`, applicationID, int(typeID))
	defer rows.Close()

	var nRows int
	for rows.Next() {
		if err = rows.Scan(&nRows); err != nil {
			return weft.InternalServerError(err)
		}
	}

	// missing applicationID or typePK should return a 404 while no data can be 200.  See GeoNet/mtr#214
	if nRows == 0 {
		return &weft.NotFound
	}

	rows.Close()

	switch resolution {
	case "minute":
		rows, err = dbR.Query(`SELECT instancePK, typePK, date_trunc('`+resolution+`',time) as t, avg(value)
		FROM app.metric
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND typePK = $2
		AND time >= $3 AND time <= $4
		GROUP BY date_trunc('`+resolution+`',time), typePK, instancePK
		ORDER BY t ASC`, applicationID, int(typeID), timeRange[0], timeRange[1])
	case "five_minutes":
		rows, err = dbR.Query(`SELECT instancePK, typePK,
		date_trunc('hour', time) + extract(minute from time)::int / 5 * interval '5 min' as t, avg(value)
		FROM app.metric
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND typePK = $2
		AND time >= $3 AND time <= $4
		GROUP BY date_trunc('hour', time) + extract(minute from time)::int / 5 * interval '5 min', typePK, instancePK
		ORDER BY t ASC`, applicationID, int(typeID), timeRange[0], timeRange[1])
	case "hour":
		rows, err = dbR.Query(`SELECT instancePK, typePK, date_trunc('`+resolution+`',time) as t, avg(value)
		FROM app.metric
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND typePK = $2
		AND time >= $3 AND time <= $4
		GROUP BY date_trunc('`+resolution+`',time), typePK, instancePK
		ORDER BY t ASC`, applicationID, int(typeID), timeRange[0], timeRange[1])
	case "full":
		rows, err = dbR.Query(`SELECT instancePK, typePK, time as t, value
		FROM app.metric
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND typePK = $2
		AND time >= $3 AND time <= $4
		ORDER BY time ASC`, applicationID, int(typeID), timeRange[0], timeRange[1])
	default:
		return weft.InternalServerError(fmt.Errorf("invalid resolution: %s", resolution))
	}
	if err != nil {
		return weft.InternalServerError(err)
	}

	defer rows.Close()

	var t time.Time
	var typePK, instancePK int
	var avg float64
	var instanceID string
	pts := make(map[InstanceMetric][]ts.Point)

	for rows.Next() {
		if err = rows.Scan(&instancePK, &typePK, &t, &avg); err != nil {
			return weft.InternalServerError(err)
		}
		key := InstanceMetric{instancePK: instancePK, typePK: typePK}
		pts[key] = append(pts[key], ts.Point{DateTime: t, Value: avg})
	}
	rows.Close()

	instanceIDs := make(map[int]string)

	if rows, err = dbR.Query(`SELECT instancePK, instanceID FROM app.instance`); err != nil {
		return weft.InternalServerError(err)
	}

	defer rows.Close()

	for rows.Next() {
		if err = rows.Scan(&instancePK, &instanceID); err != nil {
			return weft.InternalServerError(err)
		}
		instanceIDs[instancePK] = instanceID
	}
	rows.Close()

	var keys InstanceMetrics

	for k := range pts {
		keys = append(keys, k)
	}

	sort.Sort(keys)

	var labels ts.Labels

	for i, k := range keys {
		if i > len(colours) {
			i = 0
		}

		c := colours[i]

		p.AddSeries(ts.Series{Colour: c, Points: pts[k]})
		labels = append(labels, ts.Label{Colour: c, Label: fmt.Sprintf("%s.%s", instanceIDs[k.instancePK], internal.Label(k.typePK))})
	}

	p.SetLabels(labels)

	return &weft.StatusOK

}
Example #6
0
func (a appMetric) loadMemory(applicationID, resolution string, timeRange []time.Time, p *ts.Plot) *weft.Result {
	var err error

	var rows *sql.Rows

	switch resolution {
	case "minute":
		rows, err = dbR.Query(`SELECT instancePK, typePK, date_trunc('`+resolution+`',time) as t, avg(value)
		FROM app.metric
		 WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND typePK IN (1000, 1001, 1002)
		AND time >= $2 AND time <= $3
		GROUP BY date_trunc('`+resolution+`',time), typePK, instancePK
		ORDER BY t ASC`, applicationID, timeRange[0], timeRange[1])
	case "five_minutes":
		rows, err = dbR.Query(`SELECT instancePK, typePK,
		date_trunc('hour', time) + extract(minute from time)::int / 5 * interval '5 min' as t, avg(value)
		FROM app.metric
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND typePK IN (1000, 1001, 1002)
		AND time >= $2 AND time <= $3
		GROUP BY date_trunc('hour', time) + extract(minute from time)::int / 5 * interval '5 min', typePK, instancePK
		ORDER BY t ASC`, applicationID, timeRange[0], timeRange[1])
	case "hour":
		rows, err = dbR.Query(`SELECT instancePK, typePK, date_trunc('`+resolution+`',time) as t, avg(value)
		FROM app.metric
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND typePK IN (1000, 1001, 1002)
		AND time >= $2 AND time <= $3
		GROUP BY date_trunc('`+resolution+`',time), typePK, instancePK
		ORDER BY t ASC`, applicationID, timeRange[0], timeRange[1])
	case "full":
		rows, err = dbR.Query(`SELECT instancePK, typePK, time, value
		FROM app.metric
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND typePK IN (1000, 1001, 1002)
		AND time >= $2 AND time <= $3
		ORDER BY time ASC`, applicationID, timeRange[0], timeRange[1])
	default:
		return weft.InternalServerError(fmt.Errorf("invalid resolution: %s", resolution))
	}
	if err != nil {
		return weft.InternalServerError(err)
	}

	defer rows.Close()

	var t time.Time
	var typePK, instancePK int
	var avg float64
	var instanceID string
	pts := make(map[InstanceMetric][]ts.Point)

	for rows.Next() {
		if err = rows.Scan(&instancePK, &typePK, &t, &avg); err != nil {
			fmt.Println(err)
			return weft.InternalServerError(err)
		}
		key := InstanceMetric{instancePK: instancePK, typePK: typePK}
		pts[key] = append(pts[key], ts.Point{DateTime: t, Value: avg})
	}
	rows.Close()

	instanceIDs := make(map[int]string)

	if rows, err = dbR.Query(`SELECT instancePK, instanceID FROM app.instance`); err != nil {
		return weft.InternalServerError(err)
	}

	defer rows.Close()

	for rows.Next() {
		if err = rows.Scan(&instancePK, &instanceID); err != nil {
			return weft.InternalServerError(err)
		}
		instanceIDs[instancePK] = instanceID
	}
	rows.Close()

	var labels ts.Labels

	for k := range pts {
		p.AddSeries(ts.Series{Colour: internal.Colour(k.typePK), Points: pts[k]})
		labels = append(labels, ts.Label{Colour: internal.Colour(k.typePK), Label: fmt.Sprintf("%s.%s", instanceIDs[k.instancePK], strings.TrimPrefix(internal.Label(k.typePK), `Mem `))})
	}

	p.SetLabels(labels)

	return &weft.StatusOK

}
Example #7
0
func appMetricCsv(r *http.Request, h http.Header, b *bytes.Buffer) *weft.Result {
	a := appMetric{}

	v := r.URL.Query()
	applicationID := v.Get("applicationID")

	resolution := v.Get("resolution")
	if resolution == "" {
		resolution = "minute"
	}

	var timeRange []time.Time
	var err error
	if timeRange, err = parseTimeRange(v); err != nil {
		return weft.InternalServerError(err)
	}

	// the Plot type holds all the data used to plot svgs, we'll create a CSV from the labels and point values
	var p ts.Plot

	switch v.Get("group") {
	case "counters":
		if res := a.loadCounters(applicationID, resolution, timeRange, &p); !res.Ok {
			return res
		}
	case "timers":
		// "full" resolution for timers is 90th percentile max per minute over fourty days
		sourceID := v.Get("sourceID")
		if sourceID != "" {
			if res := a.loadTimersWithSourceID(applicationID, sourceID, resolution, timeRange, &p); !res.Ok {
				return res
			}
		} else {
			if res := a.loadTimers(applicationID, resolution, timeRange, &p); !res.Ok {
				return res
			}
		}
	case "memory":
		if res := a.loadMemory(applicationID, resolution, timeRange, &p); !res.Ok {
			return res
		}
	case "objects":
		if res := a.loadAppMetrics(applicationID, resolution, internal.MemHeapObjects, timeRange, &p); !res.Ok {
			return res
		}
	case "routines":
		if res := a.loadAppMetrics(applicationID, resolution, internal.Routines, timeRange, &p); !res.Ok {
			return res
		}
	default:
		return weft.BadRequest("invalid value for group")
	}

	// CSV headers, the first label is always time
	labels := p.GetLabels()
	var headers []string
	for _, label := range labels {
		headers = append(headers, label.Label)
	}

	// Labels can be in random order so keep a sorted list but with time always at 0
	sort.Strings(headers)
	headers = append([]string{"time"}, headers...)

	values := make(map[time.Time]map[string]float64)
	ts := times{} // maintaining an ordered and unique list of times in the map

	// add all points to a map to collect duplicate times with different column names
	allData := p.GetSeries()
	for i, d := range allData {
		points := d.Series.Points
		for _, point := range points {
			if _, ok := values[point.DateTime]; ok == false {
				values[point.DateTime] = map[string]float64{labels[i].Label: point.Value}
				ts = append(ts, point.DateTime)
			} else {
				v := values[point.DateTime]
				v[labels[i].Label] = point.Value
			}
		}
	}

	if len(values) == 0 {
		return &weft.StatusOK
	}

	w := csv.NewWriter(b)
	sort.Sort(ts)
	for i, t := range ts {

		// CSV headers
		if i == 0 {
			if err = w.Write(headers); err != nil {
				return weft.InternalServerError(err)
			}
		}

		fields := []string{t.Format(DYGRAPH_TIME_FORMAT)}

		// CSV data
		// start at index 1: because we've already written out the time
		for _, colName := range headers[1:] {

			val, ok := values[t][colName]
			if ok {
				fields = append(fields, fmt.Sprintf("%.2f", val))
			} else {
				// Dygraphs expected an empty CSV field for missing data.
				fields = append(fields, "")
			}
		}

		if err = w.Write(fields); err != nil {
			return weft.InternalServerError(err)
		}
	}

	w.Flush()
	if err := w.Error(); err != nil {
		return weft.InternalServerError(err)
	}

	return &weft.StatusOK
}
Example #8
0
func (a appMetric) loadTimersWithSourceID(applicationID, sourceID, resolution string, timeRange []time.Time, p *ts.Plot) *weft.Result {
	var err error

	var rows *sql.Rows

	switch resolution {
	case "minute":
		rows, err = dbR.Query(`SELECT date_trunc('minute',time) as t, avg(average), max(fifty), max(ninety), sum(count)
		FROM app.timer
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND sourcePK = (SELECT sourcePK from app.source WHERE sourceID = $2)
		AND time >= $3 AND time <= $4
		GROUP BY date_trunc('minute',time)
		ORDER BY t ASC`, applicationID, sourceID, timeRange[0], timeRange[1])
	case "five_minutes":
		rows, err = dbR.Query(`SELECT
		date_trunc('hour', time) + extract(minute from time)::int / 5 * interval '5 min' as t,
		avg(average), max(fifty), max(ninety), sum(count)
		FROM app.timer
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND sourcePK = (SELECT sourcePK from app.source WHERE sourceID = $2)
		AND time >= $3 AND time <= $4
		GROUP BY date_trunc('hour', time) + extract(minute from time)::int / 5 * interval '5 min'
		ORDER BY t ASC`, applicationID, sourceID, timeRange[0], timeRange[1])
	case "hour":
		rows, err = dbR.Query(`SELECT date_trunc('hour',time) as t, avg(average), max(fifty), max(ninety), sum(count)
		FROM app.timer
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND sourcePK = (SELECT sourcePK from app.source WHERE sourceID = $2)
		AND time >= $3 AND time <= $4
		GROUP BY date_trunc('hour', time)
		ORDER BY t ASC`, applicationID, sourceID, timeRange[0], timeRange[1])
	case "full":
		rows, err = dbR.Query(`SELECT time, average, fifty, ninety, count
		FROM app.timer
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND sourcePK = (SELECT sourcePK from app.source WHERE sourceID = $2)
		AND time >= $3 AND time <= $4
		ORDER BY time ASC`, applicationID, sourceID, timeRange[0], timeRange[1])
	default:
		return weft.InternalServerError(fmt.Errorf("invalid resolution: %s", resolution))
	}
	if err != nil {
		return weft.InternalServerError(err)
	}

	defer rows.Close()

	var t time.Time
	var avg_mean float64
	var max_fifty, max_ninety, n int
	pts := make(map[internal.ID][]ts.Point)

	for rows.Next() {
		if err = rows.Scan(&t, &avg_mean, &max_fifty, &max_ninety, &n); err != nil {
			return weft.InternalServerError(err)
		}

		pts[internal.AvgMean] = append(pts[internal.AvgMean], ts.Point{DateTime: t, Value: avg_mean})
		pts[internal.MaxFifty] = append(pts[internal.MaxFifty], ts.Point{DateTime: t, Value: float64(max_fifty)})
		pts[internal.MaxNinety] = append(pts[internal.MaxNinety], ts.Point{DateTime: t, Value: float64(max_ninety)})
	}
	rows.Close()

	var labels ts.Labels

	for k, v := range pts {
		i := int(k)
		p.AddSeries(ts.Series{Points: v, Colour: internal.Colour(i)})
		labels = append(labels, ts.Label{Label: fmt.Sprintf("%s (n=%d)", strings.TrimPrefix(internal.Label(i), `main.`), len(v)), Colour: internal.Colour(i)})
	}

	p.SetLabels(labels)

	return &weft.StatusOK

}
Example #9
0
func (a appMetric) loadTimers(applicationID, resolution string, timeRange []time.Time, p *ts.Plot) *weft.Result {
	var err error

	var rows *sql.Rows

	switch resolution {
	case "minute":
		rows, err = dbR.Query(`SELECT sourcePK, date_trunc('`+resolution+`',time) as t, max(ninety), sum(count)
		FROM app.timer
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND time >= $2 AND time <= $3
		GROUP BY date_trunc('`+resolution+`',time), sourcePK
		ORDER BY t ASC`, applicationID, timeRange[0], timeRange[1])
	case "five_minutes":
		rows, err = dbR.Query(`SELECT sourcePK,
		date_trunc('hour', time) + extract(minute from time)::int / 5 * interval '5 min' as t,
		max(ninety), sum(count)
		FROM app.timer
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND time >= $2 AND time <= $3
		GROUP BY date_trunc('hour', time) + extract(minute from time)::int / 5 * interval '5 min', sourcePK
		ORDER BY t ASC`, applicationID, timeRange[0], timeRange[1])
	case "hour":
		rows, err = dbR.Query(`SELECT sourcePK, date_trunc('`+resolution+`',time) as t, max(ninety), sum(count)
		FROM app.timer
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND time >= $2 AND time <= $3
		GROUP BY date_trunc('`+resolution+`',time), sourcePK
		ORDER BY t ASC`, applicationID, timeRange[0], timeRange[1])
	case "full":
		rows, err = dbR.Query(`SELECT sourcePK, time, ninety, count
		FROM app.timer
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND time >= $2 AND time <= $3
		ORDER BY time ASC`, applicationID, timeRange[0], timeRange[1])
	default:
		return weft.InternalServerError(fmt.Errorf("invalid resolution: %s", resolution))
	}
	if err != nil {
		return weft.InternalServerError(err)
	}

	defer rows.Close()

	var t time.Time
	var sourcePK, max, n int
	var sourceID string
	pts := make(map[int][]ts.Point)
	total := make(map[int]int) // track the total counts (call) for each timer.

	for rows.Next() {
		if err = rows.Scan(&sourcePK, &t, &max, &n); err != nil {
			return weft.InternalServerError(err)
		}
		pts[sourcePK] = append(pts[sourcePK], ts.Point{DateTime: t, Value: float64(max)})
		total[sourcePK] += n
	}
	rows.Close()

	sourceIDs := make(map[int]string)

	if rows, err = dbR.Query(`SELECT sourcePK, sourceID FROM app.source`); err != nil {
		return weft.InternalServerError(err)
	}

	defer rows.Close()

	for rows.Next() {
		if err = rows.Scan(&sourcePK, &sourceID); err != nil {
			return weft.InternalServerError(err)
		}
		sourceIDs[sourcePK] = sourceID
	}
	rows.Close()

	// sort the sourcePKs based on number of calls.
	keys := rank(total)

	var labels ts.Labels

	for _, k := range keys {
		p.AddSeries(ts.Series{Points: pts[k.Key], Colour: "#e34a33"})
		labels = append(labels, ts.Label{Label: fmt.Sprintf("%s (n=%d)", strings.TrimPrefix(sourceIDs[k.Key], `main.`), total[k.Key]), Colour: "lightgrey"})
	}

	p.SetLabels(labels)

	return &weft.StatusOK

}
Example #10
0
func (a appMetric) loadCounters(applicationID, resolution string, timeRange []time.Time, p *ts.Plot) *weft.Result {
	var err error
	var rows *sql.Rows

	switch resolution {
	case "minute":
		rows, err = dbR.Query(`SELECT typePK, date_trunc('`+resolution+`',time) as t, sum(count)
		FROM app.counter
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND time >= $2 AND time <= $3
		GROUP BY date_trunc('`+resolution+`',time), typePK
		ORDER BY t ASC`, applicationID, timeRange[0], timeRange[1])
	case "five_minutes":
		rows, err = dbR.Query(`SELECT typePK,
		date_trunc('hour', time) + extract(minute from time)::int / 5 * interval '5 min' as t, sum(count)
		FROM app.counter
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND time >= $2 AND time <= $3
		GROUP BY date_trunc('hour', time) + extract(minute from time)::int / 5 * interval '5 min', typePK
		ORDER BY t ASC`, applicationID, timeRange[0], timeRange[1])
	case "hour":
		rows, err = dbR.Query(`SELECT typePK, date_trunc('`+resolution+`',time) as t, sum(count)
		FROM app.counter
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND time >= $2 AND time <= $3
		GROUP BY date_trunc('`+resolution+`',time), typePK
		ORDER BY t ASC`, applicationID, timeRange[0], timeRange[1])
	case "full":
		rows, err = dbR.Query(`SELECT typePK, time, count
		FROM app.counter
		JOIN app.type USING (typepk)
		WHERE applicationPK = (SELECT applicationPK from app.application WHERE applicationID = $1)
		AND time >= $2 AND time <= $3
		ORDER BY time ASC`, applicationID, timeRange[0], timeRange[1])
	default:
		return weft.InternalServerError(fmt.Errorf("invalid resolution: %s", resolution))
	}
	if err != nil {
		return weft.InternalServerError(err)
	}

	defer rows.Close()

	var t time.Time
	var typePK, count int
	pts := make(map[int][]ts.Point)
	total := make(map[int]int)

	for rows.Next() {
		if err = rows.Scan(&typePK, &t, &count); err != nil {
			return weft.InternalServerError(err)
		}
		pts[typePK] = append(pts[typePK], ts.Point{DateTime: t, Value: float64(count)})
		total[typePK] += count
	}
	rows.Close()

	var keys []int
	for k := range pts {
		keys = append(keys, k)
	}

	sort.Ints(keys)

	var labels ts.Labels

	for _, k := range keys {
		p.AddSeries(ts.Series{Colour: internal.Colour(k), Points: pts[k]})
		labels = append(labels, ts.Label{Colour: internal.Colour(k), Label: fmt.Sprintf("%s (n=%d)", internal.Label(k), total[k])})
	}

	p.SetLabels(labels)

	return &weft.StatusOK

}
Example #11
0
func appMetricSvg(r *http.Request, h http.Header, b *bytes.Buffer) *weft.Result {
	a := appMetric{}

	v := r.URL.Query()

	applicationID := v.Get("applicationID")

	var p ts.Plot

	resolution := v.Get("resolution")

	switch resolution {
	case "", "minute":
		resolution = "minute"
		p.SetXAxis(time.Now().UTC().Add(time.Hour*-12), time.Now().UTC())
		p.SetXLabel("12 hours")
	case "five_minutes":
		p.SetXAxis(time.Now().UTC().Add(time.Hour*-24*3), time.Now().UTC())
		p.SetXLabel("48 hours")
	case "hour":
		p.SetXAxis(time.Now().UTC().Add(time.Hour*-24*28), time.Now().UTC())
		p.SetXLabel("4 weeks")
	default:
		return weft.BadRequest("invalid value for resolution")
	}

	var err error

	if v.Get("yrange") != "" {
		y := strings.Split(v.Get("yrange"), `,`)

		var ymin, ymax float64

		if len(y) != 2 {
			return weft.BadRequest("invalid yrange query param.")
		}
		if ymin, err = strconv.ParseFloat(y[0], 64); err != nil {
			return weft.BadRequest("invalid yrange query param.")
		}
		if ymax, err = strconv.ParseFloat(y[1], 64); err != nil {
			return weft.BadRequest("invalid yrange query param.")
		}
		p.SetYAxis(ymin, ymax)
	}

	resTitle := resolution
	resTitle = strings.Replace(resTitle, "_", " ", -1)
	resTitle = strings.Title(resTitle)

	var timeRange []time.Time
	if timeRange, err = parseTimeRange(v); err != nil {
		return weft.InternalServerError(err)
	}

	switch v.Get("group") {
	case "counters":
		if res := a.loadCounters(applicationID, resolution, timeRange, &p); !res.Ok {
			return res
		}

		p.SetTitle(fmt.Sprintf("Application: %s, Metric: Counters - Sum per %s", applicationID, resTitle))
		err = ts.MixedAppMetrics.Draw(p, b)
	case "timers":
		sourceID := v.Get("sourceID")
		if sourceID != "" {
			if res := a.loadTimersWithSourceID(applicationID, sourceID, resolution, timeRange, &p); !res.Ok {
				return res
			}

			p.SetTitle(fmt.Sprintf("Application: %s, Source: %s, Metric: Timers - 90th Percentile (ms) per %s",
				applicationID, sourceID, resTitle))
		} else {
			if res := a.loadTimers(applicationID, resolution, timeRange, &p); !res.Ok {
				return res
			}

			p.SetTitle(fmt.Sprintf("Application: %s, Metric: Timers - 90th Percentile (ms) - Max per %s",
				applicationID, resTitle))
		}
		err = ts.ScatterAppTimers.Draw(p, b)
	case "memory":
		if res := a.loadMemory(applicationID, resolution, timeRange, &p); !res.Ok {
			return res
		}

		p.SetTitle(fmt.Sprintf("Application: %s, Metric: Memory (bytes) - Average per %s",
			applicationID, resTitle))
		err = ts.LineAppMetrics.Draw(p, b)
	case "objects":
		if res := a.loadAppMetrics(applicationID, resolution, internal.MemHeapObjects, timeRange, &p); !res.Ok {
			return res
		}

		p.SetTitle(fmt.Sprintf("Application: %s, Metric: Memory Heap Objects (n) - Average per %s",
			applicationID, resTitle))
		err = ts.LineAppMetrics.Draw(p, b)
	case "routines":
		if res := a.loadAppMetrics(applicationID, resolution, internal.Routines, timeRange, &p); !res.Ok {
			return res
		}
		p.SetTitle(fmt.Sprintf("Application: %s, Metric: Routines (n) - Average per %s",
			applicationID, resTitle))
		err = ts.LineAppMetrics.Draw(p, b)
	default:
		return weft.BadRequest("invalid value for type")
	}

	if err != nil {
		return weft.InternalServerError(err)
	}

	return &weft.StatusOK

}
Example #12
0
func dataLatencyPlot(siteID, typeID, resolution string, plotter ts.SVGPlot, b *bytes.Buffer) *weft.Result {
	var err error
	// we need the sitePK often so read it once.
	var sitePK int
	if err = dbR.QueryRow(`SELECT sitePK FROM data.site WHERE siteID = $1`,
		siteID).Scan(&sitePK); err != nil {
		if err == sql.ErrNoRows {
			return &weft.NotFound
		}
		return weft.InternalServerError(err)
	}

	var typePK int
	var scale float64
	var display string

	if err = dbR.QueryRow(`SELECT typePK, scale, display FROM data.type WHERE typeID = $1`,
		typeID).Scan(&typePK, &scale, &display); err != nil {
		if err == sql.ErrNoRows {
			return &weft.NotFound
		}
		return weft.InternalServerError(err)
	}

	var p ts.Plot

	p.SetUnit(display)

	var lower, upper int

	if err := dbR.QueryRow(`SELECT lower,upper FROM data.latency_threshold
		WHERE sitePK = $1 AND typePK = $2`,
		sitePK, typePK).Scan(&lower, &upper); err != nil && err != sql.ErrNoRows {
		return weft.InternalServerError(err)
	}

	if !(lower == 0 && upper == 0) {
		p.SetThreshold(float64(lower)*scale, float64(upper)*scale)
	}

	var tags []string
	var rows *sql.Rows

	if rows, err = dbR.Query(`SELECT tag FROM data.latency_tag JOIN mtr.tag USING (tagpk) WHERE
		sitePK = $1 AND typePK = $2
		ORDER BY tag asc`,
		sitePK, typePK); err != nil {
		return weft.InternalServerError(err)
	}

	defer rows.Close()

	for rows.Next() {
		var s string
		if err = rows.Scan(&s); err != nil {
			return weft.InternalServerError(err)
		}
		tags = append(tags, s)
	}
	rows.Close()

	p.SetSubTitle("Tags: " + strings.Join(tags, ","))
	p.SetTitle(fmt.Sprintf("Site: %s - %s", siteID, strings.Title(typeID)))

	// TODO - loading avg(mean) at each resolution.  Need to add max(fifty) and max(ninety) when there are some values.
	switch resolution {
	case "minute":
		p.SetXAxis(time.Now().UTC().Add(time.Hour*-12), time.Now().UTC())
		p.SetXLabel("12 hours")
	case "five_minutes":
		p.SetXAxis(time.Now().UTC().Add(time.Hour*-24*2), time.Now().UTC())
		p.SetXLabel("48 hours")
	case "hour":
		p.SetXAxis(time.Now().UTC().Add(time.Hour*-24*28), time.Now().UTC())
		p.SetXLabel("4 weeks")
	default:
		return weft.BadRequest("invalid resolution")
	}

	if err != nil {
		return weft.InternalServerError(err)
	}

	var timeRange []time.Time
	if timeRange, err = defaultTimeRange(resolution); err != nil {
		weft.InternalServerError(err)
	}

	rows, err = queryLatencyRows(sitePK, typePK, resolution, timeRange)
	defer rows.Close()

	pts := make(map[internal.ID]([]ts.Point))

	var mean float64
	var fifty int
	var ninety int
	var pt ts.Point

	for rows.Next() {
		if err = rows.Scan(&pt.DateTime, &mean, &fifty, &ninety); err != nil {
			return weft.InternalServerError(err)
		}
		pt.Value = mean * scale
		pts[internal.Mean] = append(pts[internal.Mean], pt)

		pt.Value = float64(fifty) * scale
		pts[internal.Fifty] = append(pts[internal.Fifty], pt)

		pt.Value = float64(ninety) * scale
		pts[internal.Ninety] = append(pts[internal.Ninety], pt)

	}
	rows.Close()

	// Add the latest value to the plot - this may be different to the average at minute or hour resolution.
	if err = dbR.QueryRow(`SELECT time, mean, fifty, ninety FROM data.latency WHERE
			sitePK = $1 AND typePK = $2
			ORDER BY time DESC
			LIMIT 1`,
		sitePK, typePK).Scan(&pt.DateTime, &mean, &fifty, &ninety); err != nil {
		return weft.InternalServerError(err)
	}

	pt.Value = mean * scale
	pts[internal.Mean] = append(pts[internal.Mean], pt)
	p.SetLatest(pt, internal.Colour(int(internal.Mean)))

	// No latest label for fifty and ninety
	pt.Value = float64(fifty) * scale
	pts[internal.Fifty] = append(pts[internal.Fifty], pt)

	pt.Value = float64(ninety) * scale
	pts[internal.Ninety] = append(pts[internal.Ninety], pt)

	for k, v := range pts {
		i := int(k)
		p.AddSeries(ts.Series{Colour: internal.Colour(i), Points: v})
	}

	// We need the labels in the order of "Mean, Fifty, Ninety" so put labels in the "range pts" won't work
	var labels ts.Labels
	labels = append(labels, ts.Label{Label: internal.Label(int(internal.Mean)), Colour: internal.Colour(int(internal.Mean))})
	labels = append(labels, ts.Label{Label: internal.Label(int(internal.Fifty)), Colour: internal.Colour(int(internal.Fifty))})
	labels = append(labels, ts.Label{Label: internal.Label(int(internal.Ninety)), Colour: internal.Colour(int(internal.Ninety))})
	p.SetLabels(labels)

	if err = plotter.Draw(p, b); err != nil {
		return weft.InternalServerError(err)
	}

	return &weft.StatusOK
}