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 }