You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ne...@apache.org on 2017/03/02 03:52:53 UTC

[1/3] incubator-trafficcontrol git commit: Move TM2 datarequest to its own package

Repository: incubator-trafficcontrol
Updated Branches:
  refs/heads/master fe50d0902 -> d5692bb46


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/manager/datarequest.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/manager/datarequest.go b/traffic_monitor_golang/traffic_monitor/manager/datarequest.go
deleted file mode 100644
index b94b610..0000000
--- a/traffic_monitor_golang/traffic_monitor/manager/datarequest.go
+++ /dev/null
@@ -1,1295 +0,0 @@
-package manager
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import (
-	"encoding/json"
-	"fmt"
-	"math"
-	"net/http"
-	"net/url"
-	"runtime"
-	"sort"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/util"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/cache"
-	ds "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/deliveryservice"
-	dsdata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/deliveryservicedata"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/health"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/srvhttp"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
-	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
-	towrap "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopswrapper"
-	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
-)
-
-// JSONEvents represents the structure we wish to serialize to JSON, for Events.
-type JSONEvents struct {
-	Events []health.Event `json:"events"`
-}
-
-// CacheState represents the available state of a cache.
-type CacheState struct {
-	Value bool `json:"value"`
-}
-
-// APIPeerStates contains the data to be returned for an API call to get the peer states of a Traffic Monitor. This contains common API data returned by most endpoints, and a map of peers, to caches' states.
-type APIPeerStates struct {
-	srvhttp.CommonAPIData
-	Peers map[enum.TrafficMonitorName]map[enum.CacheName][]CacheState `json:"peers"`
-}
-
-// CacheStatus contains summary stat data about the given cache.
-// TODO make fields nullable, so error fields can be omitted, letting API callers still get updates for unerrored fields
-type CacheStatus struct {
-	Type         *string  `json:"type,omitempty"`
-	Status       *string  `json:"status,omitempty"`
-	StatusPoller *string  `json:"status_poller,omitempty"`
-	LoadAverage  *float64 `json:"load_average,omitempty"`
-	// QueryTimeMilliseconds is the time it took this app to perform a complete query and process the data, end-to-end, for the latest health query.
-	QueryTimeMilliseconds *int64 `json:"query_time_ms,omitempty"`
-	// HealthTimeMilliseconds is the time it took to make the HTTP request and get back the full response, for the latest health query.
-	HealthTimeMilliseconds *int64 `json:"health_time_ms,omitempty"`
-	// StatTimeMilliseconds is the time it took to make the HTTP request and get back the full response, for the latest stat query.
-	StatTimeMilliseconds *int64 `json:"stat_time_ms,omitempty"`
-	// StatSpanMilliseconds is the length of time between completing the most recent two stat queries. This can be used as a rough gauge of the end-to-end query processing time.
-	StatSpanMilliseconds *int64 `json:"stat_span_ms,omitempty"`
-	// HealthSpanMilliseconds is the length of time between completing the most recent two health queries. This can be used as a rough gauge of the end-to-end query processing time.
-	HealthSpanMilliseconds *int64   `json:"health_span_ms,omitempty"`
-	BandwidthKbps          *float64 `json:"bandwidth_kbps,omitempty"`
-	BandwidthCapacityKbps  *float64 `json:"bandwidth_capacity_kbps,omitempty"`
-	ConnectionCount        *int64   `json:"connection_count,omitempty"`
-}
-
-// CacheStatFilter fulfills the cache.Filter interface, for filtering stats. See the `NewCacheStatFilter` documentation for details on which query parameters are used to filter.
-type CacheStatFilter struct {
-	historyCount int
-	statsToUse   map[string]struct{}
-	wildcard     bool
-	cacheType    enum.CacheType
-	hosts        map[enum.CacheName]struct{}
-	cacheTypes   map[enum.CacheName]enum.CacheType
-}
-
-// UseCache returns whether the given cache is in the filter.
-func (f *CacheStatFilter) UseCache(name enum.CacheName) bool {
-	if _, inHosts := f.hosts[name]; len(f.hosts) != 0 && !inHosts {
-		return false
-	}
-	if f.cacheType != enum.CacheTypeInvalid && f.cacheTypes[name] != f.cacheType {
-		return false
-	}
-	return true
-}
-
-// UseStat returns whether the given stat is in the filter.
-func (f *CacheStatFilter) UseStat(statName string) bool {
-	if len(f.statsToUse) == 0 {
-		return true
-	}
-	if !f.wildcard {
-		_, ok := f.statsToUse[statName]
-		return ok
-	}
-	for statToUse := range f.statsToUse {
-		if strings.Contains(statName, statToUse) {
-			return true
-		}
-	}
-	return false
-}
-
-// WithinStatHistoryMax returns whether the given history index is less than the max history of this filter.
-func (f *CacheStatFilter) WithinStatHistoryMax(n int) bool {
-	if f.historyCount == 0 {
-		return true
-	}
-	if n <= f.historyCount {
-		return true
-	}
-	return false
-}
-
-// NewCacheStatFilter takes the HTTP query parameters and creates a CacheStatFilter which fulfills the `cache.Filter` interface, filtering according to the query parameters passed.
-// Query parameters used are `hc`, `stats`, `wildcard`, `type`, and `hosts`.
-// If `hc` is 0, all history is returned. If `hc` is empty, 1 history is returned.
-// If `stats` is empty, all stats are returned.
-// If `wildcard` is empty, `stats` is considered exact.
-// If `type` is empty, all cache types are returned.
-func NewCacheStatFilter(path string, params url.Values, cacheTypes map[enum.CacheName]enum.CacheType) (cache.Filter, error) {
-	validParams := map[string]struct{}{
-		"hc":       struct{}{},
-		"stats":    struct{}{},
-		"wildcard": struct{}{},
-		"type":     struct{}{},
-		"hosts":    struct{}{},
-		"cache":    struct{}{},
-	}
-	if len(params) > len(validParams) {
-		return nil, fmt.Errorf("invalid query parameters")
-	}
-	for param := range params {
-		if _, ok := validParams[param]; !ok {
-			return nil, fmt.Errorf("invalid query parameter '%v'", param)
-		}
-	}
-
-	historyCount := 1
-	if paramHc, exists := params["hc"]; exists && len(paramHc) > 0 {
-		v, err := strconv.Atoi(paramHc[0])
-		if err == nil {
-			historyCount = v
-		}
-	}
-
-	statsToUse := map[string]struct{}{}
-	if paramStats, exists := params["stats"]; exists && len(paramStats) > 0 {
-		commaStats := strings.Split(paramStats[0], ",")
-		for _, stat := range commaStats {
-			statsToUse[stat] = struct{}{}
-		}
-	}
-
-	wildcard := false
-	if paramWildcard, exists := params["wildcard"]; exists && len(paramWildcard) > 0 {
-		wildcard, _ = strconv.ParseBool(paramWildcard[0]) // ignore errors, error => false
-	}
-
-	cacheType := enum.CacheTypeInvalid
-	if paramType, exists := params["type"]; exists && len(paramType) > 0 {
-		cacheType = enum.CacheTypeFromString(paramType[0])
-		if cacheType == enum.CacheTypeInvalid {
-			return nil, fmt.Errorf("invalid query parameter type '%v' - valid types are: {edge, mid}", paramType[0])
-		}
-	}
-
-	hosts := map[enum.CacheName]struct{}{}
-	if paramHosts, exists := params["hosts"]; exists && len(paramHosts) > 0 {
-		commaHosts := strings.Split(paramHosts[0], ",")
-		for _, host := range commaHosts {
-			hosts[enum.CacheName(host)] = struct{}{}
-		}
-	}
-	if paramHosts, exists := params["cache"]; exists && len(paramHosts) > 0 {
-		commaHosts := strings.Split(paramHosts[0], ",")
-		for _, host := range commaHosts {
-			hosts[enum.CacheName(host)] = struct{}{}
-		}
-	}
-
-	pathArgument := getPathArgument(path)
-	if pathArgument != "" {
-		hosts[enum.CacheName(pathArgument)] = struct{}{}
-	}
-
-	// parameters without values are considered hosts, e.g. `?my-cache-0`
-	for maybeHost, val := range params {
-		if len(val) == 0 || (len(val) == 1 && val[0] == "") {
-			hosts[enum.CacheName(maybeHost)] = struct{}{}
-		}
-	}
-
-	return &CacheStatFilter{
-		historyCount: historyCount,
-		statsToUse:   statsToUse,
-		wildcard:     wildcard,
-		cacheType:    cacheType,
-		hosts:        hosts,
-		cacheTypes:   cacheTypes,
-	}, nil
-}
-
-// This is the "spirit" of how TM1.0 works; hack to extract a path argument to filter data (/publish/SomeEndpoint/:argument).
-func getPathArgument(path string) string {
-	pathParts := strings.Split(path, "/")
-	if len(pathParts) >= 4 {
-		return pathParts[3]
-	}
-
-	return ""
-}
-
-// DSStatFilter fulfills the cache.Filter interface, for filtering stats. See the `NewDSStatFilter` documentation for details on which query parameters are used to filter.
-type DSStatFilter struct {
-	historyCount     int
-	statsToUse       map[string]struct{}
-	wildcard         bool
-	dsType           enum.DSType
-	deliveryServices map[enum.DeliveryServiceName]struct{}
-	dsTypes          map[enum.DeliveryServiceName]enum.DSType
-}
-
-// UseDeliveryService returns whether the given delivery service is in this filter.
-func (f *DSStatFilter) UseDeliveryService(name enum.DeliveryServiceName) bool {
-	if _, inDSes := f.deliveryServices[name]; len(f.deliveryServices) != 0 && !inDSes {
-		return false
-	}
-	if f.dsType != enum.DSTypeInvalid && f.dsTypes[name] != f.dsType {
-		return false
-	}
-	return true
-}
-
-// UseStat returns whether the given stat is in this filter.
-func (f *DSStatFilter) UseStat(statName string) bool {
-	if len(f.statsToUse) == 0 {
-		return true
-	}
-	if !f.wildcard {
-		_, ok := f.statsToUse[statName]
-		return ok
-	}
-	for statToUse := range f.statsToUse {
-		if strings.Contains(statName, statToUse) {
-			return true
-		}
-	}
-	return false
-}
-
-// WithinStatHistoryMax returns whether the given history index is less than the max history of this filter.
-func (f *DSStatFilter) WithinStatHistoryMax(n int) bool {
-	if f.historyCount == 0 {
-		return true
-	}
-	if n <= f.historyCount {
-		return true
-	}
-	return false
-}
-
-// NewDSStatFilter takes the HTTP query parameters and creates a cache.Filter, filtering according to the query parameters passed.
-// Query parameters used are `hc`, `stats`, `wildcard`, `type`, and `deliveryservices`.
-// If `hc` is 0, all history is returned. If `hc` is empty, 1 history is returned.
-// If `stats` is empty, all stats are returned.
-// If `wildcard` is empty, `stats` is considered exact.
-// If `type` is empty, all types are returned.
-func NewDSStatFilter(path string, params url.Values, dsTypes map[enum.DeliveryServiceName]enum.DSType) (dsdata.Filter, error) {
-	validParams := map[string]struct{}{"hc": struct{}{}, "stats": struct{}{}, "wildcard": struct{}{}, "type": struct{}{}, "deliveryservices": struct{}{}}
-	if len(params) > len(validParams) {
-		return nil, fmt.Errorf("invalid query parameters")
-	}
-	for param := range params {
-		if _, ok := validParams[param]; !ok {
-			return nil, fmt.Errorf("invalid query parameter '%v'", param)
-		}
-	}
-
-	historyCount := 1
-	if paramHc, exists := params["hc"]; exists && len(paramHc) > 0 {
-		v, err := strconv.Atoi(paramHc[0])
-		if err == nil {
-			historyCount = v
-		}
-	}
-
-	statsToUse := map[string]struct{}{}
-	if paramStats, exists := params["stats"]; exists && len(paramStats) > 0 {
-		commaStats := strings.Split(paramStats[0], ",")
-		for _, stat := range commaStats {
-			statsToUse[stat] = struct{}{}
-		}
-	}
-
-	wildcard := false
-	if paramWildcard, exists := params["wildcard"]; exists && len(paramWildcard) > 0 {
-		wildcard, _ = strconv.ParseBool(paramWildcard[0]) // ignore errors, error => false
-	}
-
-	dsType := enum.DSTypeInvalid
-	if paramType, exists := params["type"]; exists && len(paramType) > 0 {
-		dsType = enum.DSTypeFromString(paramType[0])
-		if dsType == enum.DSTypeInvalid {
-			return nil, fmt.Errorf("invalid query parameter type '%v' - valid types are: {http, dns}", paramType[0])
-		}
-	}
-
-	deliveryServices := map[enum.DeliveryServiceName]struct{}{}
-	// TODO rename 'hosts' to 'names' for consistency
-	if paramNames, exists := params["deliveryservices"]; exists && len(paramNames) > 0 {
-		commaNames := strings.Split(paramNames[0], ",")
-		for _, name := range commaNames {
-			deliveryServices[enum.DeliveryServiceName(name)] = struct{}{}
-		}
-	}
-
-	pathArgument := getPathArgument(path)
-	if pathArgument != "" {
-		deliveryServices[enum.DeliveryServiceName(pathArgument)] = struct{}{}
-	}
-
-	// parameters without values are considered names, e.g. `?my-cache-0` or `?my-delivery-service`
-	for maybeName, val := range params {
-		if len(val) == 0 || (len(val) == 1 && val[0] == "") {
-			deliveryServices[enum.DeliveryServiceName(maybeName)] = struct{}{}
-		}
-	}
-
-	return &DSStatFilter{
-		historyCount:     historyCount,
-		statsToUse:       statsToUse,
-		wildcard:         wildcard,
-		dsType:           dsType,
-		deliveryServices: deliveryServices,
-		dsTypes:          dsTypes,
-	}, nil
-}
-
-// PeerStateFilter fulfills the cache.Filter interface, for filtering stats. See the `NewPeerStateFilter` documentation for details on which query parameters are used to filter.
-type PeerStateFilter struct {
-	historyCount int
-	cachesToUse  map[enum.CacheName]struct{}
-	peersToUse   map[enum.TrafficMonitorName]struct{}
-	wildcard     bool
-	cacheType    enum.CacheType
-	cacheTypes   map[enum.CacheName]enum.CacheType
-}
-
-// UsePeer returns whether the given Traffic Monitor peer is in this filter.
-func (f *PeerStateFilter) UsePeer(name enum.TrafficMonitorName) bool {
-	if _, inPeers := f.peersToUse[name]; len(f.peersToUse) != 0 && !inPeers {
-		return false
-	}
-	return true
-}
-
-// UseCache returns whether the given cache is in this filter.
-func (f *PeerStateFilter) UseCache(name enum.CacheName) bool {
-	if f.cacheType != enum.CacheTypeInvalid && f.cacheTypes[name] != f.cacheType {
-		return false
-	}
-
-	if len(f.cachesToUse) == 0 {
-		return true
-	}
-
-	if !f.wildcard {
-		_, ok := f.cachesToUse[name]
-		return ok
-	}
-	for cacheToUse := range f.cachesToUse {
-		if strings.Contains(string(name), string(cacheToUse)) {
-			return true
-		}
-	}
-	return false
-}
-
-// WithinStatHistoryMax returns whether the given history index is less than the max history of this filter.
-func (f *PeerStateFilter) WithinStatHistoryMax(n int) bool {
-	if f.historyCount == 0 {
-		return true
-	}
-	if n <= f.historyCount {
-		return true
-	}
-	return false
-}
-
-// NewPeerStateFilter takes the HTTP query parameters and creates a cache.Filter, filtering according to the query parameters passed.
-// Query parameters used are `hc`, `stats`, `wildcard`, `typep`, and `hosts`. The `stats` param filters caches. The `hosts` param filters peer Traffic Monitors. The `type` param filters cache types (edge, mid).
-// If `hc` is 0, all history is returned. If `hc` is empty, 1 history is returned.
-// If `stats` is empty, all stats are returned.
-// If `wildcard` is empty, `stats` is considered exact.
-// If `type` is empty, all cache types are returned.
-func NewPeerStateFilter(path string, params url.Values, cacheTypes map[enum.CacheName]enum.CacheType) (*PeerStateFilter, error) {
-	// TODO change legacy `stats` and `hosts` to `caches` and `monitors` (or `peers`).
-	validParams := map[string]struct{}{"hc": struct{}{}, "stats": struct{}{}, "wildcard": struct{}{}, "type": struct{}{}, "peers": struct{}{}}
-	if len(params) > len(validParams) {
-		return nil, fmt.Errorf("invalid query parameters")
-	}
-	for param := range params {
-		if _, ok := validParams[param]; !ok {
-			return nil, fmt.Errorf("invalid query parameter '%v'", param)
-		}
-	}
-
-	historyCount := 1
-	if paramHc, exists := params["hc"]; exists && len(paramHc) > 0 {
-		v, err := strconv.Atoi(paramHc[0])
-		if err == nil {
-			historyCount = v
-		}
-	}
-
-	cachesToUse := map[enum.CacheName]struct{}{}
-	// TODO rename 'stats' to 'caches'
-	if paramStats, exists := params["stats"]; exists && len(paramStats) > 0 {
-		commaStats := strings.Split(paramStats[0], ",")
-		for _, stat := range commaStats {
-			cachesToUse[enum.CacheName(stat)] = struct{}{}
-		}
-	}
-
-	wildcard := false
-	if paramWildcard, exists := params["wildcard"]; exists && len(paramWildcard) > 0 {
-		wildcard, _ = strconv.ParseBool(paramWildcard[0]) // ignore errors, error => false
-	}
-
-	cacheType := enum.CacheTypeInvalid
-	if paramType, exists := params["type"]; exists && len(paramType) > 0 {
-		cacheType = enum.CacheTypeFromString(paramType[0])
-		if cacheType == enum.CacheTypeInvalid {
-			return nil, fmt.Errorf("invalid query parameter type '%v' - valid types are: {edge, mid}", paramType[0])
-		}
-	}
-
-	peersToUse := map[enum.TrafficMonitorName]struct{}{}
-	if paramNames, exists := params["peers"]; exists && len(paramNames) > 0 {
-		commaNames := strings.Split(paramNames[0], ",")
-		for _, name := range commaNames {
-			peersToUse[enum.TrafficMonitorName(name)] = struct{}{}
-		}
-	}
-
-	pathArgument := getPathArgument(path)
-	if pathArgument != "" {
-		peersToUse[enum.TrafficMonitorName(pathArgument)] = struct{}{}
-	}
-
-	// parameters without values are considered names, e.g. `?my-cache-0` or `?my-delivery-service`
-	for maybeName, val := range params {
-		if len(val) == 0 || (len(val) == 1 && val[0] == "") {
-			peersToUse[enum.TrafficMonitorName(maybeName)] = struct{}{}
-		}
-	}
-
-	return &PeerStateFilter{
-		historyCount: historyCount,
-		cachesToUse:  cachesToUse,
-		wildcard:     wildcard,
-		cacheType:    cacheType,
-		peersToUse:   peersToUse,
-		cacheTypes:   cacheTypes,
-	}, nil
-}
-
-// HandleErr takes an error, and the request type it came from, and logs. It is ok to call with a nil error, in which case this is a no-op.
-func HandleErr(errorCount threadsafe.Uint, reqPath string, err error) {
-	if err == nil {
-		return
-	}
-	errorCount.Inc()
-	log.Errorf("Request Error: %v\n", fmt.Errorf(reqPath+": %v", err))
-}
-
-// WrapErrCode takes the body, err, and log context (errorCount, reqPath). It logs and deals with any error, and returns the appropriate bytes and response code for the `srvhttp`. It notably returns InternalServerError status on any error, for security reasons.
-func WrapErrCode(errorCount threadsafe.Uint, reqPath string, body []byte, err error) ([]byte, int) {
-	if err == nil {
-		return body, http.StatusOK
-	}
-	HandleErr(errorCount, reqPath, err)
-	return nil, http.StatusInternalServerError
-}
-
-// WrapBytes takes a function which cannot error and returns only bytes, and wraps it as a http.HandlerFunc. The errContext is logged if the write fails, and should be enough information to trace the problem (function name, endpoint, request parameters, etc).
-func WrapBytes(f func() []byte, contentType string) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("Content-Type", contentType)
-		log.Write(w, f(), r.URL.EscapedPath())
-	}
-}
-
-// WrapErr takes a function which returns bytes and an error, and wraps it as a http.HandlerFunc. If the error is nil, the bytes are written with Status OK. Else, the error is logged, and InternalServerError is returned as the response code. If you need to return a different response code (for example, StatusBadRequest), call wrapRespCode.
-func WrapErr(errorCount threadsafe.Uint, f func() ([]byte, error), contentType string) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		bytes, err := f()
-		_, code := WrapErrCode(errorCount, r.URL.EscapedPath(), bytes, err)
-		w.Header().Set("Content-Type", contentType)
-		w.WriteHeader(code)
-		log.Write(w, bytes, r.URL.EscapedPath())
-	}
-}
-
-// SrvFunc is a function which takes URL parameters, and returns the requested data, and a response code. Note it does not take the full http.Request, and does not have the path. SrvFunc functions should be called via dispatch, and any additional data needed should be closed via a lambda.
-// TODO split params and path into 2 separate wrappers?
-// TODO change to simply take the http.Request?
-type SrvFunc func(params url.Values, path string) ([]byte, int)
-
-// WrapParams takes a SrvFunc and wraps it as an http.HandlerFunc. Note if the SrvFunc returns 0 bytes, an InternalServerError is returned, and the response code is ignored, for security reasons. If an error response code is necessary, return bytes to that effect, for example, "Bad Request". DO NOT return informational messages regarding internal server errors; these should be logged, and only a 500 code returned to the client, for security reasons.
-func WrapParams(f SrvFunc, contentType string) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		bytes, code := f(r.URL.Query(), r.URL.EscapedPath())
-		if len(bytes) > 0 {
-			w.Header().Set("Content-Type", contentType)
-			w.WriteHeader(code)
-			if _, err := w.Write(bytes); err != nil {
-				log.Warnf("received error writing data request %v: %v\n", r.URL.EscapedPath(), err)
-			}
-		} else {
-			w.WriteHeader(http.StatusInternalServerError)
-			if _, err := w.Write([]byte("Internal Server Error")); err != nil {
-				log.Warnf("received error writing data request %v: %v\n", r.URL.EscapedPath(), err)
-			}
-		}
-	}
-}
-
-func WrapAgeErr(errorCount threadsafe.Uint, f func() ([]byte, time.Time, error), contentType string) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		bytes, contentTime, err := f()
-		_, code := WrapErrCode(errorCount, r.URL.EscapedPath(), bytes, err)
-		w.Header().Set("Content-Type", contentType)
-		w.Header().Set("Age", fmt.Sprintf("%.0f", time.Since(contentTime).Seconds()))
-		w.WriteHeader(code)
-		log.Write(w, bytes, r.URL.EscapedPath())
-	}
-}
-
-func srvTRConfig(opsConfig OpsConfigThreadsafe, toSession towrap.ITrafficOpsSession) ([]byte, time.Time, error) {
-	cdnName := opsConfig.Get().CdnName
-	if toSession == nil {
-		return nil, time.Time{}, fmt.Errorf("Unable to connect to Traffic Ops")
-	}
-	if cdnName == "" {
-		return nil, time.Time{}, fmt.Errorf("No CDN Configured")
-	}
-	return toSession.LastCRConfig(cdnName)
-}
-
-func srvTRState(params url.Values, localStates peer.CRStatesThreadsafe, combinedStates peer.CRStatesThreadsafe) ([]byte, error) {
-	if _, raw := params["raw"]; raw {
-		return srvTRStateSelf(localStates)
-	}
-	return srvTRStateDerived(combinedStates)
-}
-
-func srvTRStateDerived(combinedStates peer.CRStatesThreadsafe) ([]byte, error) {
-	return peer.CrstatesMarshall(combinedStates.Get())
-}
-
-func srvTRStateSelf(localStates peer.CRStatesThreadsafe) ([]byte, error) {
-	return peer.CrstatesMarshall(localStates.Get())
-}
-
-func srvCacheStats(params url.Values, errorCount threadsafe.Uint, path string, toData todata.TODataThreadsafe, statResultHistory threadsafe.ResultStatHistory, statInfoHistory threadsafe.ResultInfoHistory, monitorConfig TrafficMonitorConfigMapThreadsafe, combinedStates peer.CRStatesThreadsafe, statMaxKbpses threadsafe.CacheKbpses) ([]byte, int) {
-	filter, err := NewCacheStatFilter(path, params, toData.Get().ServerTypes)
-	if err != nil {
-		HandleErr(errorCount, path, err)
-		return []byte(err.Error()), http.StatusBadRequest
-	}
-	bytes, err := cache.StatsMarshall(statResultHistory.Get(), statInfoHistory.Get(), combinedStates.Get(), monitorConfig.Get(), statMaxKbpses.Get(), filter, params)
-	return WrapErrCode(errorCount, path, bytes, err)
-}
-
-func srvDSStats(params url.Values, errorCount threadsafe.Uint, path string, toData todata.TODataThreadsafe, dsStats threadsafe.DSStatsReader) ([]byte, int) {
-	filter, err := NewDSStatFilter(path, params, toData.Get().DeliveryServiceTypes)
-	if err != nil {
-		HandleErr(errorCount, path, err)
-		return []byte(err.Error()), http.StatusBadRequest
-	}
-	bytes, err := json.Marshal(dsStats.Get().JSON(filter, params))
-	return WrapErrCode(errorCount, path, bytes, err)
-}
-
-func srvEventLog(events health.ThreadsafeEvents) ([]byte, error) {
-	return json.Marshal(JSONEvents{Events: events.Get()})
-}
-
-func srvPeerStates(params url.Values, errorCount threadsafe.Uint, path string, toData todata.TODataThreadsafe, peerStates peer.CRStatesPeersThreadsafe) ([]byte, int) {
-	filter, err := NewPeerStateFilter(path, params, toData.Get().ServerTypes)
-	if err != nil {
-		HandleErr(errorCount, path, err)
-		return []byte(err.Error()), http.StatusBadRequest
-	}
-	bytes, err := json.Marshal(createAPIPeerStates(peerStates.GetCrstates(), filter, params))
-	return WrapErrCode(errorCount, path, bytes, err)
-}
-
-func srvStats(staticAppData StaticAppData, healthPollInterval time.Duration, lastHealthDurations DurationMapThreadsafe, fetchCount threadsafe.Uint, healthIteration threadsafe.Uint, errorCount threadsafe.Uint, peerStates peer.CRStatesPeersThreadsafe) ([]byte, error) {
-	return getStats(staticAppData, healthPollInterval, lastHealthDurations.Get(), fetchCount.Get(), healthIteration.Get(), errorCount.Get(), peerStates)
-}
-
-func srvConfigDoc(opsConfig OpsConfigThreadsafe) ([]byte, error) {
-	opsConfigCopy := opsConfig.Get()
-	// if the password is blank, leave it blank, so callers can see it's missing.
-	if opsConfigCopy.Password != "" {
-		opsConfigCopy.Password = "*****"
-	}
-	return json.Marshal(opsConfigCopy)
-}
-
-// TODO determine if this should use peerStates
-func srvAPICacheCount(localStates peer.CRStatesThreadsafe) []byte {
-	return []byte(strconv.Itoa(len(localStates.Get().Caches)))
-}
-
-func srvAPICacheAvailableCount(localStates peer.CRStatesThreadsafe) []byte {
-	return []byte(strconv.Itoa(cacheAvailableCount(localStates.Get().Caches)))
-}
-
-func srvAPICacheDownCount(localStates peer.CRStatesThreadsafe, monitorConfig TrafficMonitorConfigMapThreadsafe) []byte {
-	return []byte(strconv.Itoa(cacheDownCount(localStates.Get().Caches, monitorConfig.Get().TrafficServer)))
-}
-
-func srvAPIVersion(staticAppData StaticAppData) []byte {
-	s := "traffic_monitor-" + staticAppData.Version + "."
-	if len(staticAppData.GitRevision) > 6 {
-		s += staticAppData.GitRevision[:6]
-	} else {
-		s += staticAppData.GitRevision
-	}
-	return []byte(s)
-}
-
-func srvAPITrafficOpsURI(opsConfig OpsConfigThreadsafe) []byte {
-	return []byte(opsConfig.Get().Url)
-}
-func srvAPICacheStates(toData todata.TODataThreadsafe, statInfoHistory threadsafe.ResultInfoHistory, statResultHistory threadsafe.ResultStatHistory, healthHistory threadsafe.ResultHistory, lastHealthDurations DurationMapThreadsafe, localStates peer.CRStatesThreadsafe, lastStats threadsafe.LastStats, localCacheStatus threadsafe.CacheAvailableStatus, statMaxKbpses threadsafe.CacheKbpses) ([]byte, error) {
-	return json.Marshal(createCacheStatuses(toData.Get().ServerTypes, statInfoHistory.Get(), statResultHistory.Get(), healthHistory.Get(), lastHealthDurations.Get(), localStates.Get().Caches, lastStats.Get(), localCacheStatus, statMaxKbpses))
-}
-
-func srvAPIBandwidthKbps(toData todata.TODataThreadsafe, lastStats threadsafe.LastStats) []byte {
-	kbpsStats := lastStats.Get()
-	sum := float64(0.0)
-	for _, data := range kbpsStats.Caches {
-		sum += data.Bytes.PerSec / ds.BytesPerKilobit
-	}
-	return []byte(fmt.Sprintf("%f", sum))
-}
-func srvAPIBandwidthCapacityKbps(statMaxKbpses threadsafe.CacheKbpses) []byte {
-	maxKbpses := statMaxKbpses.Get()
-	cap := int64(0)
-	for _, kbps := range maxKbpses {
-		cap += kbps
-	}
-	return []byte(fmt.Sprintf("%d", cap))
-}
-
-// WrapUnpolledCheck wraps an http.HandlerFunc, returning ServiceUnavailable if any caches are unpolled; else, calling the wrapped func.
-func wrapUnpolledCheck(unpolledCaches threadsafe.UnpolledCaches, errorCount threadsafe.Uint, f http.HandlerFunc) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		if unpolledCaches.Any() {
-			HandleErr(errorCount, r.URL.EscapedPath(), fmt.Errorf("service still starting, some caches unpolled: %v", unpolledCaches.UnpolledCaches()))
-			w.WriteHeader(http.StatusServiceUnavailable)
-			log.Write(w, []byte("Service Unavailable"), r.URL.EscapedPath())
-			return
-		}
-		f(w, r)
-	}
-}
-
-const ContentTypeJSON = "application/json"
-
-// addTrailingEndpoints adds endpoints with trailing slashes to the given dispatch map. Without this, Go will match `route` and `route/` differently.
-func addTrailingSlashEndpoints(dispatchMap map[string]http.HandlerFunc) map[string]http.HandlerFunc {
-	for route, handler := range dispatchMap {
-		if strings.HasSuffix(route, "/") {
-			continue
-		}
-		dispatchMap[route+"/"] = handler
-	}
-	return dispatchMap
-}
-
-// MakeDispatchMap returns the map of paths to http.HandlerFuncs for dispatching.
-func MakeDispatchMap(
-	opsConfig OpsConfigThreadsafe,
-	toSession towrap.ITrafficOpsSession,
-	localStates peer.CRStatesThreadsafe,
-	peerStates peer.CRStatesPeersThreadsafe,
-	combinedStates peer.CRStatesThreadsafe,
-	statInfoHistory threadsafe.ResultInfoHistory,
-	statResultHistory threadsafe.ResultStatHistory,
-	statMaxKbpses threadsafe.CacheKbpses,
-	healthHistory threadsafe.ResultHistory,
-	dsStats threadsafe.DSStatsReader,
-	events health.ThreadsafeEvents,
-	staticAppData StaticAppData,
-	healthPollInterval time.Duration,
-	lastHealthDurations DurationMapThreadsafe,
-	fetchCount threadsafe.Uint,
-	healthIteration threadsafe.Uint,
-	errorCount threadsafe.Uint,
-	toData todata.TODataThreadsafe,
-	localCacheStatus threadsafe.CacheAvailableStatus,
-	lastStats threadsafe.LastStats,
-	unpolledCaches threadsafe.UnpolledCaches,
-	monitorConfig TrafficMonitorConfigMapThreadsafe,
-) map[string]http.HandlerFunc {
-
-	// wrap composes all universal wrapper functions. Right now, it's only the UnpolledCheck, but there may be others later. For example, security headers.
-	wrap := func(f http.HandlerFunc) http.HandlerFunc {
-		return wrapUnpolledCheck(unpolledCaches, errorCount, f)
-	}
-
-	dispatchMap := map[string]http.HandlerFunc{
-		"/publish/CrConfig": wrap(WrapAgeErr(errorCount, func() ([]byte, time.Time, error) {
-			return srvTRConfig(opsConfig, toSession)
-		}, ContentTypeJSON)),
-		"/publish/CrStates": wrap(WrapParams(func(params url.Values, path string) ([]byte, int) {
-			bytes, err := srvTRState(params, localStates, combinedStates)
-			return WrapErrCode(errorCount, path, bytes, err)
-		}, ContentTypeJSON)),
-		"/publish/CacheStats": wrap(WrapParams(func(params url.Values, path string) ([]byte, int) {
-			return srvCacheStats(params, errorCount, path, toData, statResultHistory, statInfoHistory, monitorConfig, combinedStates, statMaxKbpses)
-		}, ContentTypeJSON)),
-		"/publish/DsStats": wrap(WrapParams(func(params url.Values, path string) ([]byte, int) {
-			return srvDSStats(params, errorCount, path, toData, dsStats)
-		}, ContentTypeJSON)),
-		"/publish/EventLog": wrap(WrapErr(errorCount, func() ([]byte, error) {
-			return srvEventLog(events)
-		}, ContentTypeJSON)),
-		"/publish/PeerStates": wrap(WrapParams(func(params url.Values, path string) ([]byte, int) {
-			return srvPeerStates(params, errorCount, path, toData, peerStates)
-		}, ContentTypeJSON)),
-		"/publish/Stats": wrap(WrapErr(errorCount, func() ([]byte, error) {
-			return srvStats(staticAppData, healthPollInterval, lastHealthDurations, fetchCount, healthIteration, errorCount, peerStates)
-		}, ContentTypeJSON)),
-		"/publish/ConfigDoc": wrap(WrapErr(errorCount, func() ([]byte, error) {
-			return srvConfigDoc(opsConfig)
-		}, ContentTypeJSON)),
-		"/publish/StatSummary": wrap(WrapParams(func(params url.Values, path string) ([]byte, int) {
-			return srvStatSummary(params, errorCount, path, toData, statResultHistory)
-		}, ContentTypeJSON)),
-		"/api/cache-count": wrap(WrapBytes(func() []byte {
-			return srvAPICacheCount(localStates)
-		}, ContentTypeJSON)),
-		"/api/cache-available-count": wrap(WrapBytes(func() []byte {
-			return srvAPICacheAvailableCount(localStates)
-		}, ContentTypeJSON)),
-		"/api/cache-down-count": wrap(WrapBytes(func() []byte {
-			return srvAPICacheDownCount(localStates, monitorConfig)
-		}, ContentTypeJSON)),
-		"/api/version": wrap(WrapBytes(func() []byte {
-			return srvAPIVersion(staticAppData)
-		}, ContentTypeJSON)),
-		"/api/traffic-ops-uri": wrap(WrapBytes(func() []byte {
-			return srvAPITrafficOpsURI(opsConfig)
-		}, ContentTypeJSON)),
-		"/api/cache-statuses": wrap(WrapErr(errorCount, func() ([]byte, error) {
-			return srvAPICacheStates(toData, statInfoHistory, statResultHistory, healthHistory, lastHealthDurations, localStates, lastStats, localCacheStatus, statMaxKbpses)
-		}, ContentTypeJSON)),
-		"/api/bandwidth-kbps": wrap(WrapBytes(func() []byte {
-			return srvAPIBandwidthKbps(toData, lastStats)
-		}, ContentTypeJSON)),
-		"/api/bandwidth-capacity-kbps": wrap(WrapBytes(func() []byte {
-			return srvAPIBandwidthCapacityKbps(statMaxKbpses)
-		}, ContentTypeJSON)),
-		"/api/monitor-config": wrap(WrapErr(errorCount, func() ([]byte, error) {
-			return srvMonitorConfig(monitorConfig)
-		}, ContentTypeJSON)),
-	}
-	return addTrailingSlashEndpoints(dispatchMap)
-}
-
-func srvMonitorConfig(mcThs TrafficMonitorConfigMapThreadsafe) ([]byte, error) {
-	return json.Marshal(mcThs.Get())
-}
-
-// latestResultInfoTimeMS returns the length of time in milliseconds that it took to request the most recent non-errored result info.
-func latestResultInfoTimeMS(cacheName enum.CacheName, history cache.ResultInfoHistory) (int64, error) {
-	results, ok := history[cacheName]
-	if !ok {
-		return 0, fmt.Errorf("cache %v has no history", cacheName)
-	}
-	if len(results) == 0 {
-		return 0, fmt.Errorf("cache %v history empty", cacheName)
-	}
-	result := cache.ResultInfo{}
-	foundResult := false
-	for _, r := range results {
-		if r.Error == nil {
-			result = r
-			foundResult = true
-			break
-		}
-	}
-	if !foundResult {
-		return 0, fmt.Errorf("cache %v No unerrored result", cacheName)
-	}
-	return int64(result.RequestTime / time.Millisecond), nil
-}
-
-// latestResultTimeMS returns the length of time in milliseconds that it took to request the most recent non-errored result.
-func latestResultTimeMS(cacheName enum.CacheName, history map[enum.CacheName][]cache.Result) (int64, error) {
-
-	results, ok := history[cacheName]
-	if !ok {
-		return 0, fmt.Errorf("cache %v has no history", cacheName)
-	}
-	if len(results) == 0 {
-		return 0, fmt.Errorf("cache %v history empty", cacheName)
-	}
-	result := cache.Result{}
-	foundResult := false
-	for _, r := range results {
-		if r.Error == nil {
-			result = r
-			foundResult = true
-			break
-		}
-	}
-	if !foundResult {
-		return 0, fmt.Errorf("cache %v No unerrored result", cacheName)
-	}
-	return int64(result.RequestTime / time.Millisecond), nil
-}
-
-func latestQueryTimeMS(cacheName enum.CacheName, lastDurations map[enum.CacheName]time.Duration) (int64, error) {
-	queryTime, ok := lastDurations[cacheName]
-	if !ok {
-		return 0, fmt.Errorf("cache %v not in last durations\n", cacheName)
-	}
-	return int64(queryTime / time.Millisecond), nil
-}
-
-// resultSpanMS returns the length of time between the most recent two results. That is, how long could the cache have been down before we would have noticed it? Note this returns the time between the most recent two results, irrespective if they errored.
-// Note this is unrelated to the Stat Span field.
-func resultSpanMS(cacheName enum.CacheName, history map[enum.CacheName][]cache.Result) (int64, error) {
-	results, ok := history[cacheName]
-	if !ok {
-		return 0, fmt.Errorf("cache %v has no history", cacheName)
-	}
-	if len(results) == 0 {
-		return 0, fmt.Errorf("cache %v history empty", cacheName)
-	}
-	if len(results) < 2 {
-		return 0, fmt.Errorf("cache %v history only has one result, can't compute span between results", cacheName)
-	}
-
-	latestResult := results[0]
-	penultimateResult := results[1]
-	span := latestResult.Time.Sub(penultimateResult.Time)
-	return int64(span / time.Millisecond), nil
-}
-
-// resultSpanMS returns the length of time between the most recent two results. That is, how long could the cache have been down before we would have noticed it? Note this returns the time between the most recent two results, irrespective if they errored.
-// Note this is unrelated to the Stat Span field.
-func infoResultSpanMS(cacheName enum.CacheName, history cache.ResultInfoHistory) (int64, error) {
-	results, ok := history[cacheName]
-	if !ok {
-		return 0, fmt.Errorf("cache %v has no history", cacheName)
-	}
-	if len(results) == 0 {
-		return 0, fmt.Errorf("cache %v history empty", cacheName)
-	}
-	if len(results) < 2 {
-		return 0, fmt.Errorf("cache %v history only has one result, can't compute span between results", cacheName)
-	}
-
-	latestResult := results[0]
-	penultimateResult := results[1]
-	span := latestResult.Time.Sub(penultimateResult.Time)
-	return int64(span / time.Millisecond), nil
-}
-
-func createCacheStatuses(
-	cacheTypes map[enum.CacheName]enum.CacheType,
-	statInfoHistory cache.ResultInfoHistory,
-	statResultHistory cache.ResultStatHistory,
-	healthHistory map[enum.CacheName][]cache.Result,
-	lastHealthDurations map[enum.CacheName]time.Duration,
-	cacheStates map[enum.CacheName]peer.IsAvailable,
-	lastStats dsdata.LastStats,
-	localCacheStatusThreadsafe threadsafe.CacheAvailableStatus,
-	statMaxKbpses threadsafe.CacheKbpses,
-) map[enum.CacheName]CacheStatus {
-	conns := createCacheConnections(statResultHistory)
-	statii := map[enum.CacheName]CacheStatus{}
-	localCacheStatus := localCacheStatusThreadsafe.Get().Copy() // TODO test whether copy is necessary
-	maxKbpses := statMaxKbpses.Get()
-
-	for cacheName, cacheType := range cacheTypes {
-		infoHistory, ok := statInfoHistory[cacheName]
-		if !ok {
-			log.Infof("createCacheStatuses stat info history missing cache %s\n", cacheName)
-			continue
-		}
-
-		if len(infoHistory) < 1 {
-			log.Infof("createCacheStatuses stat info history empty for cache %s\n", cacheName)
-			continue
-		}
-
-		log.Debugf("createCacheStatuses NOT empty for cache %s\n", cacheName)
-
-		loadAverage := &infoHistory[0].Vitals.LoadAvg
-
-		healthQueryTime, err := latestQueryTimeMS(cacheName, lastHealthDurations)
-		if err != nil {
-			log.Infof("Error getting cache %v health query time: %v\n", cacheName, err)
-		}
-
-		statTime, err := latestResultInfoTimeMS(cacheName, statInfoHistory)
-		if err != nil {
-			log.Infof("Error getting cache %v stat result time: %v\n", cacheName, err)
-		}
-
-		healthTime, err := latestResultTimeMS(cacheName, healthHistory)
-		if err != nil {
-			log.Infof("Error getting cache %v health result time: %v\n", cacheName, err)
-		}
-
-		statSpan, err := infoResultSpanMS(cacheName, statInfoHistory)
-		if err != nil {
-			log.Infof("Error getting cache %v stat span: %v\n", cacheName, err)
-		}
-
-		healthSpan, err := resultSpanMS(cacheName, healthHistory)
-		if err != nil {
-			log.Infof("Error getting cache %v health span: %v\n", cacheName, err)
-		}
-
-		var kbps *float64
-		if lastStat, ok := lastStats.Caches[cacheName]; !ok {
-			log.Infof("cache not in last kbps cache %s\n", cacheName)
-		} else {
-			kbpsVal := lastStat.Bytes.PerSec / float64(ds.BytesPerKilobit)
-			kbps = &kbpsVal
-		}
-
-		var maxKbps *float64
-		if v, ok := maxKbpses[cacheName]; !ok {
-			log.Infof("cache not in max kbps cache %s\n", cacheName)
-		} else {
-			fv := float64(v)
-			maxKbps = &fv
-		}
-
-		var connections *int64
-		connectionsVal, ok := conns[cacheName]
-		if !ok {
-			log.Infof("cache not in connections %s\n", cacheName)
-		} else {
-			connections = &connectionsVal
-		}
-
-		var status *string
-		var statusPoller *string
-		statusVal, ok := localCacheStatus[cacheName]
-		if !ok {
-			log.Infof("cache not in statuses %s\n", cacheName)
-		} else {
-			statusString := statusVal.Status + " - "
-
-			// this should match the event string, use as the default if possible
-			if statusVal.Why != "" {
-				statusString = statusVal.Why
-			} else if statusVal.Available {
-				statusString += "available"
-			} else {
-				statusString += fmt.Sprintf("unavailable (%s)", statusVal.Why)
-			}
-
-			status = &statusString
-			statusPoller = &statusVal.Poller
-		}
-
-		cacheTypeStr := string(cacheType)
-		statii[cacheName] = CacheStatus{
-			Type:                   &cacheTypeStr,
-			LoadAverage:            loadAverage,
-			QueryTimeMilliseconds:  &healthQueryTime,
-			StatTimeMilliseconds:   &statTime,
-			HealthTimeMilliseconds: &healthTime,
-			StatSpanMilliseconds:   &statSpan,
-			HealthSpanMilliseconds: &healthSpan,
-			BandwidthKbps:          kbps,
-			BandwidthCapacityKbps:  maxKbps,
-			ConnectionCount:        connections,
-			Status:                 status,
-			StatusPoller:           statusPoller,
-		}
-	}
-	return statii
-}
-
-func createCacheConnections(statResultHistory cache.ResultStatHistory) map[enum.CacheName]int64 {
-	conns := map[enum.CacheName]int64{}
-	for server, history := range statResultHistory {
-		vals, ok := history["proxy.process.http.current_client_connections"]
-		if !ok || len(vals) < 1 {
-			continue
-		}
-
-		v, ok := vals[0].Val.(float64)
-		if !ok {
-			continue // TODO log warning? error?
-		}
-		conns[server] = int64(v)
-	}
-	return conns
-}
-
-// cacheOfflineCount returns the total caches not available, including marked unavailable, status offline, and status admin_down
-func cacheOfflineCount(caches map[enum.CacheName]peer.IsAvailable) int {
-	count := 0
-	for _, available := range caches {
-		if !available.IsAvailable {
-			count++
-		}
-	}
-	return count
-}
-
-// cacheAvailableCount returns the total caches available, including marked available and status online
-func cacheAvailableCount(caches map[enum.CacheName]peer.IsAvailable) int {
-	return len(caches) - cacheOfflineCount(caches)
-}
-
-// cacheOfflineCount returns the total reported caches marked down, excluding status offline and admin_down.
-func cacheDownCount(caches map[enum.CacheName]peer.IsAvailable, toServers map[string]to.TrafficServer) int {
-	count := 0
-	for cache, available := range caches {
-		if !available.IsAvailable && enum.CacheStatusFromString(toServers[string(cache)].Status) == enum.CacheStatusReported {
-			count++
-		}
-	}
-	return count
-}
-
-func createAPIPeerStates(peerStates map[enum.TrafficMonitorName]peer.Crstates, filter *PeerStateFilter, params url.Values) APIPeerStates {
-	apiPeerStates := APIPeerStates{
-		CommonAPIData: srvhttp.GetCommonAPIData(params, time.Now()),
-		Peers:         map[enum.TrafficMonitorName]map[enum.CacheName][]CacheState{},
-	}
-
-	for peer, state := range peerStates {
-		if !filter.UsePeer(peer) {
-			continue
-		}
-		if _, ok := apiPeerStates.Peers[peer]; !ok {
-			apiPeerStates.Peers[peer] = map[enum.CacheName][]CacheState{}
-		}
-		peerState := apiPeerStates.Peers[peer]
-		for cache, available := range state.Caches {
-			if !filter.UseCache(cache) {
-				continue
-			}
-			peerState[cache] = []CacheState{CacheState{Value: available.IsAvailable}}
-		}
-		apiPeerStates.Peers[peer] = peerState
-	}
-	return apiPeerStates
-}
-
-type JSONStats struct {
-	Stats Stats `json:"stats"`
-}
-
-// Stats contains statistics data about this running app. Designed to be returned via an API endpoint.
-type Stats struct {
-	MaxMemoryMB                 uint64 `json:"Max Memory (MB),string"`
-	GitRevision                 string `json:"git-revision"`
-	ErrorCount                  uint64 `json:"Error Count,string"`
-	Uptime                      uint64 `json:"uptime,string"`
-	FreeMemoryMB                uint64 `json:"Free Memory (MB),string"`
-	TotalMemoryMB               uint64 `json:"Total Memory (MB),string"`
-	Version                     string `json:"version"`
-	DeployDir                   string `json:"deploy-dir"`
-	FetchCount                  uint64 `json:"Fetch Count,string"`
-	QueryIntervalDelta          int    `json:"Query Interval Delta,string"`
-	IterationCount              uint64 `json:"Iteration Count,string"`
-	Name                        string `json:"name"`
-	BuildTimestamp              string `json:"buildTimestamp"`
-	QueryIntervalTarget         int    `json:"Query Interval Target,string"`
-	QueryIntervalActual         int    `json:"Query Interval Actual,string"`
-	SlowestCache                string `json:"Slowest Cache"`
-	LastQueryInterval           int    `json:"Last Query Interval,string"`
-	Microthreads                int    `json:"Goroutines"`
-	LastGC                      string `json:"Last Garbage Collection"`
-	MemAllocBytes               uint64 `json:"Memory Bytes Allocated"`
-	MemTotalBytes               uint64 `json:"Total Bytes Allocated"`
-	MemSysBytes                 uint64 `json:"System Bytes Allocated"`
-	OldestPolledPeer            string `json:"Oldest Polled Peer"`
-	OldestPolledPeerMs          int64  `json:"Oldest Polled Peer Time (ms)"`
-	QueryInterval95thPercentile int64  `json:"Query Interval 95th Percentile (ms)"`
-}
-
-func getLongestPoll(lastHealthTimes map[enum.CacheName]time.Duration) (enum.CacheName, time.Duration) {
-	var longestCache enum.CacheName
-	var longestTime time.Duration
-	for cache, time := range lastHealthTimes {
-		if time > longestTime {
-			longestTime = time
-			longestCache = cache
-		}
-	}
-	return longestCache, longestTime
-}
-
-func oldestPeerPollTime(peerTimes map[enum.TrafficMonitorName]time.Time) (enum.TrafficMonitorName, time.Time) {
-	now := time.Now()
-	oldestTime := now
-	oldestPeer := enum.TrafficMonitorName("")
-	for p, t := range peerTimes {
-		if oldestTime.After(t) {
-			oldestTime = t
-			oldestPeer = p
-		}
-	}
-	if oldestTime == now {
-		oldestTime = time.Time{}
-	}
-	return oldestPeer, oldestTime
-}
-
-const MillisecondsPerNanosecond = int64(1000000)
-
-type Durations []time.Duration
-
-func (s Durations) Len() int {
-	return len(s)
-}
-func (s Durations) Less(i, j int) bool {
-	return s[i] < s[j]
-}
-func (s Durations) Swap(i, j int) {
-	s[i], s[j] = s[j], s[i]
-}
-
-// getCacheTimePercentile returns the given percentile of cache result times. The `percentile` should be a decimal percent, for example, for the 95th percentile pass 0.95
-func getCacheTimePercentile(lastHealthTimes map[enum.CacheName]time.Duration, percentile float64) time.Duration {
-	times := make([]time.Duration, 0, len(lastHealthTimes))
-	for _, t := range lastHealthTimes {
-		times = append(times, t)
-	}
-	sort.Sort(Durations(times))
-
-	n := int(float64(len(lastHealthTimes)) * percentile)
-
-	return times[n]
-}
-
-func getStats(staticAppData StaticAppData, pollingInterval time.Duration, lastHealthTimes map[enum.CacheName]time.Duration, fetchCount uint64, healthIteration uint64, errorCount uint64, peerStates peer.CRStatesPeersThreadsafe) ([]byte, error) {
-	longestPollCache, longestPollTime := getLongestPoll(lastHealthTimes)
-	var memStats runtime.MemStats
-	runtime.ReadMemStats(&memStats)
-
-	var s Stats
-	s.MaxMemoryMB = memStats.TotalAlloc / (1024 * 1024)
-	s.GitRevision = staticAppData.GitRevision
-	s.ErrorCount = errorCount
-	s.Uptime = uint64(time.Since(staticAppData.StartTime) / time.Second)
-	s.FreeMemoryMB = staticAppData.FreeMemoryMB
-	s.TotalMemoryMB = memStats.Alloc / (1024 * 1024) // TODO rename to "used memory" if/when nothing is using the JSON entry
-	s.Version = staticAppData.Version
-	s.DeployDir = staticAppData.WorkingDir
-	s.FetchCount = fetchCount
-	s.SlowestCache = string(longestPollCache)
-	s.IterationCount = healthIteration
-	s.Name = staticAppData.Name
-	s.BuildTimestamp = staticAppData.BuildTimestamp
-	s.QueryIntervalTarget = int(pollingInterval / time.Millisecond)
-	s.QueryIntervalActual = int(longestPollTime / time.Millisecond)
-	s.QueryIntervalDelta = s.QueryIntervalActual - s.QueryIntervalTarget
-	s.LastQueryInterval = int(math.Max(float64(s.QueryIntervalActual), float64(s.QueryIntervalTarget)))
-	s.Microthreads = runtime.NumGoroutine()
-	s.LastGC = time.Unix(0, int64(memStats.LastGC)).String()
-	s.MemAllocBytes = memStats.Alloc
-	s.MemTotalBytes = memStats.TotalAlloc
-	s.MemSysBytes = memStats.Sys
-
-	oldestPolledPeer, oldestPolledPeerTime := oldestPeerPollTime(peerStates.GetQueryTimes()) // map[enum.TrafficMonitorName]time.Time)
-	s.OldestPolledPeer = string(oldestPolledPeer)
-	s.OldestPolledPeerMs = time.Now().Sub((oldestPolledPeerTime)).Nanoseconds() / MillisecondsPerNanosecond
-
-	s.QueryInterval95thPercentile = getCacheTimePercentile(lastHealthTimes, 0.95).Nanoseconds() / MillisecondsPerNanosecond
-
-	return json.Marshal(JSONStats{Stats: s})
-}
-
-type StatSummary struct {
-	Caches map[enum.CacheName]map[string]StatSummaryStat `json:"caches"`
-	srvhttp.CommonAPIData
-}
-
-type StatSummaryStat struct {
-	DataPointCount int64   `json:"dpCount"`
-	Start          float64 `json:"start"`
-	End            float64 `json:"end"`
-	High           float64 `json:"high"`
-	Low            float64 `json:"low"`
-	Average        float64 `json:"average"`
-	StartTime      int64   `json:"startTime"`
-	EndTime        int64   `json:"endTime"`
-}
-
-func createStatSummary(statResultHistory cache.ResultStatHistory, filter cache.Filter, params url.Values) StatSummary {
-	statPrefix := "ats."
-	ss := StatSummary{
-		Caches:        map[enum.CacheName]map[string]StatSummaryStat{},
-		CommonAPIData: srvhttp.GetCommonAPIData(params, time.Now()),
-	}
-	for cache, stats := range statResultHistory {
-		if !filter.UseCache(cache) {
-			continue
-		}
-		ssStats := map[string]StatSummaryStat{}
-		for statName, statHistory := range stats {
-			if !filter.UseStat(statName) {
-				continue
-			}
-			if len(statHistory) == 0 {
-				continue
-			}
-			ssStat := StatSummaryStat{}
-			ssStat.StartTime = statHistory[len(statHistory)-1].Time.UnixNano() / MillisecondsPerNanosecond
-			ssStat.EndTime = statHistory[0].Time.UnixNano() / MillisecondsPerNanosecond
-			oldestVal, isOldestValNumeric := util.ToNumeric(statHistory[len(statHistory)-1].Val)
-			newestVal, isNewestValNumeric := util.ToNumeric(statHistory[0].Val)
-			if !isOldestValNumeric || !isNewestValNumeric {
-				continue // skip non-numeric stats
-			}
-			ssStat.Start = oldestVal
-			ssStat.End = newestVal
-			ssStat.High = newestVal
-			ssStat.Low = newestVal
-			for _, val := range statHistory {
-				fVal, ok := util.ToNumeric(val.Val)
-				if !ok {
-					log.Warnf("threshold stat %v value %v is not a number, cannot use.", statName, val.Val)
-					continue
-				}
-				for i := uint64(0); i < val.Span; i++ {
-					ssStat.DataPointCount++
-					ssStat.Average -= ssStat.Average / float64(ssStat.DataPointCount)
-					ssStat.Average += fVal / float64(ssStat.DataPointCount)
-				}
-				if fVal < ssStat.Low {
-					ssStat.Low = fVal
-				}
-				if fVal > ssStat.High {
-					ssStat.High = fVal
-				}
-			}
-			ssStats[statPrefix+statName] = ssStat
-		}
-		ss.Caches[cache] = ssStats
-	}
-	return ss
-}
-
-func srvStatSummary(params url.Values, errorCount threadsafe.Uint, path string, toData todata.TODataThreadsafe, statResultHistory threadsafe.ResultStatHistory) ([]byte, int) {
-	filter, err := NewCacheStatFilter(path, params, toData.Get().ServerTypes)
-	if err != nil {
-		HandleErr(errorCount, path, err)
-		return []byte(err.Error()), http.StatusBadRequest
-	}
-	bytes, err := json.Marshal(createStatSummary(statResultHistory.Get(), filter, params))
-	return WrapErrCode(errorCount, path, bytes, err)
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/manager/health.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/manager/health.go b/traffic_monitor_golang/traffic_monitor/manager/health.go
index a002ab4..a68070a 100644
--- a/traffic_monitor_golang/traffic_monitor/manager/health.go
+++ b/traffic_monitor_golang/traffic_monitor/manager/health.go
@@ -20,7 +20,6 @@ package manager
  */
 
 import (
-	"sync"
 	"time"
 
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
@@ -33,44 +32,6 @@ import (
 	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
 )
 
-// DurationMap represents a map of cache names to durations
-type DurationMap map[enum.CacheName]time.Duration
-
-// DurationMapThreadsafe wraps a DurationMap in an object safe for a single writer and multiple readers
-type DurationMapThreadsafe struct {
-	durationMap *DurationMap
-	m           *sync.RWMutex
-}
-
-// Copy copies this duration map.
-func (a DurationMap) Copy() DurationMap {
-	b := DurationMap{}
-	for k, v := range a {
-		b[k] = v
-	}
-	return b
-}
-
-// NewDurationMapThreadsafe returns a new DurationMapThreadsafe safe for multiple readers and a single writer goroutine.
-func NewDurationMapThreadsafe() DurationMapThreadsafe {
-	m := DurationMap{}
-	return DurationMapThreadsafe{m: &sync.RWMutex{}, durationMap: &m}
-}
-
-// Get returns the duration map. Callers MUST NOT mutate. If mutation is necessary, call DurationMap.Copy().
-func (o *DurationMapThreadsafe) Get() DurationMap {
-	o.m.RLock()
-	defer o.m.RUnlock()
-	return *o.durationMap
-}
-
-// Set sets the internal duration map. This MUST NOT be called by multiple goroutines.
-func (o *DurationMapThreadsafe) Set(d DurationMap) {
-	o.m.Lock()
-	*o.durationMap = d
-	o.m.Unlock()
-}
-
 // StartHealthResultManager starts the goroutine which listens for health results.
 // Note this polls the brief stat endpoint from ATS Astats, not the full stats.
 // This poll should be quicker and less computationally expensive for ATS, but
@@ -80,15 +41,15 @@ func StartHealthResultManager(
 	cacheHealthChan <-chan cache.Result,
 	toData todata.TODataThreadsafe,
 	localStates peer.CRStatesThreadsafe,
-	monitorConfig TrafficMonitorConfigMapThreadsafe,
+	monitorConfig threadsafe.TrafficMonitorConfigMap,
 	combinedStates peer.CRStatesThreadsafe,
 	fetchCount threadsafe.Uint,
 	errorCount threadsafe.Uint,
 	cfg config.Config,
 	events health.ThreadsafeEvents,
 	localCacheStatus threadsafe.CacheAvailableStatus,
-) (DurationMapThreadsafe, threadsafe.ResultHistory) {
-	lastHealthDurations := NewDurationMapThreadsafe()
+) (threadsafe.DurationMap, threadsafe.ResultHistory) {
+	lastHealthDurations := threadsafe.NewDurationMap()
 	healthHistory := threadsafe.NewResultHistory()
 	go healthResultManagerListen(
 		cacheHealthChan,
@@ -111,9 +72,9 @@ func healthResultManagerListen(
 	cacheHealthChan <-chan cache.Result,
 	toData todata.TODataThreadsafe,
 	localStates peer.CRStatesThreadsafe,
-	lastHealthDurations DurationMapThreadsafe,
+	lastHealthDurations threadsafe.DurationMap,
 	healthHistory threadsafe.ResultHistory,
-	monitorConfig TrafficMonitorConfigMapThreadsafe,
+	monitorConfig threadsafe.TrafficMonitorConfigMap,
 	combinedStates peer.CRStatesThreadsafe,
 	fetchCount threadsafe.Uint,
 	errorCount threadsafe.Uint,
@@ -176,8 +137,8 @@ func processHealthResult(
 	cacheHealthChan <-chan cache.Result,
 	toData todata.TODataThreadsafe,
 	localStates peer.CRStatesThreadsafe,
-	lastHealthDurationsThreadsafe DurationMapThreadsafe,
-	monitorConfig TrafficMonitorConfigMapThreadsafe,
+	lastHealthDurationsThreadsafe threadsafe.DurationMap,
+	monitorConfig threadsafe.TrafficMonitorConfigMap,
 	combinedStates peer.CRStatesThreadsafe,
 	fetchCount threadsafe.Uint,
 	errorCount threadsafe.Uint,
@@ -228,7 +189,7 @@ func processHealthResult(
 	healthHistory.Set(healthHistoryCopy)
 	// TODO determine if we should combineCrStates() here
 
-	lastHealthDurations := lastHealthDurationsThreadsafe.Get().Copy()
+	lastHealthDurations := threadsafe.CopyDurationMap(lastHealthDurationsThreadsafe.Get())
 	for _, healthResult := range results {
 		if lastHealthStart, ok := lastHealthEndTimes[healthResult.ID]; ok {
 			d := time.Since(lastHealthStart)

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/manager/manager.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/manager/manager.go b/traffic_monitor_golang/traffic_monitor/manager/manager.go
index be0505b..6df3042 100644
--- a/traffic_monitor_golang/traffic_monitor/manager/manager.go
+++ b/traffic_monitor_golang/traffic_monitor/manager/manager.go
@@ -22,7 +22,6 @@ package manager
 import (
 	"crypto/tls"
 	"net/http"
-	"time"
 
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/fetcher"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/handler"
@@ -37,23 +36,10 @@ import (
 	"github.com/davecheney/gmx"
 )
 
-// StaticAppData encapsulates data about the app available at startup
-type StaticAppData struct {
-	StartTime      time.Time
-	GitRevision    string
-	FreeMemoryMB   uint64
-	Version        string
-	WorkingDir     string
-	Name           string
-	BuildTimestamp string
-	Hostname       string
-	UserAgent      string
-}
-
 //
 // Start starts the poller and handler goroutines
 //
-func Start(opsConfigFile string, cfg config.Config, staticAppData StaticAppData) {
+func Start(opsConfigFile string, cfg config.Config, staticAppData config.StaticAppData) {
 	toSession := towrap.ITrafficOpsSession(towrap.NewTrafficOpsSessionThreadsafe(nil))
 	counters := fetcher.Counters{
 		Success: gmx.NewCounter("fetchSuccess"),

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/manager/monitorconfig.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/manager/monitorconfig.go b/traffic_monitor_golang/traffic_monitor/manager/monitorconfig.go
index 6d5748a..6e756e8 100644
--- a/traffic_monitor_golang/traffic_monitor/manager/monitorconfig.go
+++ b/traffic_monitor_golang/traffic_monitor/manager/monitorconfig.go
@@ -23,7 +23,6 @@ import (
 	"fmt"
 	"os"
 	"strings"
-	"sync"
 	"time"
 
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
@@ -31,64 +30,10 @@ import (
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/config"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
 	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
 )
 
-// CopyTrafficMonitorConfigMap returns a deep copy of the given TrafficMonitorConfigMap
-func CopyTrafficMonitorConfigMap(a *to.TrafficMonitorConfigMap) to.TrafficMonitorConfigMap {
-	b := to.TrafficMonitorConfigMap{}
-	b.TrafficServer = map[string]to.TrafficServer{}
-	b.CacheGroup = map[string]to.TMCacheGroup{}
-	b.Config = map[string]interface{}{}
-	b.TrafficMonitor = map[string]to.TrafficMonitor{}
-	b.DeliveryService = map[string]to.TMDeliveryService{}
-	b.Profile = map[string]to.TMProfile{}
-	for k, v := range a.TrafficServer {
-		b.TrafficServer[k] = v
-	}
-	for k, v := range a.CacheGroup {
-		b.CacheGroup[k] = v
-	}
-	for k, v := range a.Config {
-		b.Config[k] = v
-	}
-	for k, v := range a.TrafficMonitor {
-		b.TrafficMonitor[k] = v
-	}
-	for k, v := range a.DeliveryService {
-		b.DeliveryService[k] = v
-	}
-	for k, v := range a.Profile {
-		b.Profile[k] = v
-	}
-	return b
-}
-
-// TrafficMonitorConfigMapThreadsafe encapsulates a TrafficMonitorConfigMap safe for multiple readers and a single writer.
-type TrafficMonitorConfigMapThreadsafe struct {
-	monitorConfig *to.TrafficMonitorConfigMap
-	m             *sync.RWMutex
-}
-
-// NewTrafficMonitorConfigMapThreadsafe returns an encapsulated TrafficMonitorConfigMap safe for multiple readers and a single writer.
-func NewTrafficMonitorConfigMapThreadsafe() TrafficMonitorConfigMapThreadsafe {
-	return TrafficMonitorConfigMapThreadsafe{monitorConfig: &to.TrafficMonitorConfigMap{}, m: &sync.RWMutex{}}
-}
-
-// Get returns the TrafficMonitorConfigMap. Callers MUST NOT modify, it is not threadsafe for mutation. If mutation is necessary, call CopyTrafficMonitorConfigMap().
-func (t *TrafficMonitorConfigMapThreadsafe) Get() to.TrafficMonitorConfigMap {
-	t.m.RLock()
-	defer t.m.RUnlock()
-	return *t.monitorConfig
-}
-
-// Set sets the TrafficMonitorConfigMap. This is only safe for one writer. This MUST NOT be called by multiple threads.
-func (t *TrafficMonitorConfigMapThreadsafe) Set(c to.TrafficMonitorConfigMap) {
-	t.m.Lock()
-	*t.monitorConfig = c
-	t.m.Unlock()
-}
-
 // StartMonitorConfigManager runs the monitor config manager goroutine, and returns the threadsafe data which it sets.
 func StartMonitorConfigManager(
 	monitorConfigPollChan <-chan to.TrafficMonitorConfigMap,
@@ -98,9 +43,9 @@ func StartMonitorConfigManager(
 	peerURLSubscriber chan<- poller.HttpPollerConfig,
 	cachesChangeSubscriber chan<- struct{},
 	cfg config.Config,
-	staticAppData StaticAppData,
-) TrafficMonitorConfigMapThreadsafe {
-	monitorConfig := NewTrafficMonitorConfigMapThreadsafe()
+	staticAppData config.StaticAppData,
+) threadsafe.TrafficMonitorConfigMap {
+	monitorConfig := threadsafe.NewTrafficMonitorConfigMap()
 	go monitorConfigListen(monitorConfig,
 		monitorConfigPollChan,
 		localStates,
@@ -185,7 +130,7 @@ func getHealthPeerStatPollIntervals(monitorConfig to.TrafficMonitorConfigMap, cf
 // TODO timing, and determine if the case, or its internal `for`, should be put in a goroutine
 // TODO determine if subscribers take action on change, and change to mutexed objects if not.
 func monitorConfigListen(
-	monitorConfigTS TrafficMonitorConfigMapThreadsafe,
+	monitorConfigTS threadsafe.TrafficMonitorConfigMap,
 	monitorConfigPollChan <-chan to.TrafficMonitorConfigMap,
 	localStates peer.CRStatesThreadsafe,
 	statURLSubscriber chan<- poller.HttpPollerConfig,
@@ -193,7 +138,7 @@ func monitorConfigListen(
 	peerURLSubscriber chan<- poller.HttpPollerConfig,
 	cachesChangeSubscriber chan<- struct{},
 	cfg config.Config,
-	staticAppData StaticAppData,
+	staticAppData config.StaticAppData,
 ) {
 	defer func() {
 		if err := recover(); err != nil {

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/manager/opsconfig.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/manager/opsconfig.go b/traffic_monitor_golang/traffic_monitor/manager/opsconfig.go
index 6cbe709..d0e3e68 100644
--- a/traffic_monitor_golang/traffic_monitor/manager/opsconfig.go
+++ b/traffic_monitor_golang/traffic_monitor/manager/opsconfig.go
@@ -21,13 +21,13 @@ package manager
 
 import (
 	"fmt"
-	"sync"
 	"time"
 
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/handler"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/poller"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/config"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/datareq"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/health"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/srvhttp"
@@ -37,32 +37,6 @@ import (
 	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
 )
 
-// OpsConfigThreadsafe provides safe access for multiple reader goroutines and a single writer to a stored OpsConfig object.
-// This could be made lock-free, if the performance was necessary
-type OpsConfigThreadsafe struct {
-	opsConfig *handler.OpsConfig
-	m         *sync.RWMutex
-}
-
-// NewOpsConfigThreadsafe returns a new single-writer-multiple-reader OpsConfig
-func NewOpsConfigThreadsafe() OpsConfigThreadsafe {
-	return OpsConfigThreadsafe{m: &sync.RWMutex{}, opsConfig: &handler.OpsConfig{}}
-}
-
-// Get gets the internal OpsConfig object. This MUST NOT be modified. If modification is necessary, copy the object.
-func (o *OpsConfigThreadsafe) Get() handler.OpsConfig {
-	o.m.RLock()
-	defer o.m.RUnlock()
-	return *o.opsConfig
-}
-
-// Set sets the internal OpsConfig object. This MUST NOT be called from multiple goroutines.
-func (o *OpsConfigThreadsafe) Set(newOpsConfig handler.OpsConfig) {
-	o.m.Lock()
-	*o.opsConfig = newOpsConfig
-	o.m.Unlock()
-}
-
 // StartOpsConfigManager starts the ops config manager goroutine, returning the (threadsafe) variables which it sets.
 // Note the OpsConfigManager is in charge of the httpServer, because ops config changes trigger server changes. If other things needed to trigger server restarts, the server could be put in its own goroutine with signal channels
 func StartOpsConfigManager(
@@ -81,17 +55,17 @@ func StartOpsConfigManager(
 	lastStats threadsafe.LastStats,
 	dsStats threadsafe.DSStatsReader,
 	events health.ThreadsafeEvents,
-	staticAppData StaticAppData,
+	staticAppData config.StaticAppData,
 	healthPollInterval time.Duration,
-	lastHealthDurations DurationMapThreadsafe,
+	lastHealthDurations threadsafe.DurationMap,
 	fetchCount threadsafe.Uint,
 	healthIteration threadsafe.Uint,
 	errorCount threadsafe.Uint,
 	localCacheStatus threadsafe.CacheAvailableStatus,
 	unpolledCaches threadsafe.UnpolledCaches,
-	monitorConfig TrafficMonitorConfigMapThreadsafe,
+	monitorConfig threadsafe.TrafficMonitorConfigMap,
 	cfg config.Config,
-) OpsConfigThreadsafe {
+) threadsafe.OpsConfig {
 
 	opsConfigFileChannel := make(chan interface{})
 	opsConfigFilePoller := poller.FilePoller{
@@ -108,7 +82,7 @@ func StartOpsConfigManager(
 	go opsConfigFileHandler.Listen()
 	go opsConfigFilePoller.Poll()
 
-	opsConfig := NewOpsConfigThreadsafe()
+	opsConfig := threadsafe.NewOpsConfig()
 
 	// TODO remove change subscribers, give Threadsafes directly to the things that need them. If they only set vars, and don't actually do work on change.
 	go func() {
@@ -129,7 +103,7 @@ func StartOpsConfigManager(
 				log.Errorf("OpsConfigManager: %v\n", err)
 			}
 
-			endpoints := MakeDispatchMap(
+			endpoints := datareq.MakeDispatchMap(
 				opsConfig,
 				toSession,
 				localStates,

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/manager/stat.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/manager/stat.go b/traffic_monitor_golang/traffic_monitor/manager/stat.go
index 44547a4..6c000d5 100644
--- a/traffic_monitor_golang/traffic_monitor/manager/stat.go
+++ b/traffic_monitor_golang/traffic_monitor/manager/stat.go
@@ -41,7 +41,7 @@ func pruneHistory(history []cache.Result, limit uint64) []cache.Result {
 	return history
 }
 
-func getNewCaches(localStates peer.CRStatesThreadsafe, monitorConfigTS TrafficMonitorConfigMapThreadsafe) map[enum.CacheName]struct{} {
+func getNewCaches(localStates peer.CRStatesThreadsafe, monitorConfigTS threadsafe.TrafficMonitorConfigMap) map[enum.CacheName]struct{} {
 	monitorConfig := monitorConfigTS.Get()
 	caches := map[enum.CacheName]struct{}{}
 	for cacheName := range localStates.GetCaches() {
@@ -65,14 +65,14 @@ func StartStatHistoryManager(
 	cachesChanged <-chan struct{},
 	errorCount threadsafe.Uint,
 	cfg config.Config,
-	monitorConfig TrafficMonitorConfigMapThreadsafe,
+	monitorConfig threadsafe.TrafficMonitorConfigMap,
 	events health.ThreadsafeEvents,
 	combineState func(),
-) (threadsafe.ResultInfoHistory, threadsafe.ResultStatHistory, threadsafe.CacheKbpses, DurationMapThreadsafe, threadsafe.LastStats, threadsafe.DSStatsReader, threadsafe.UnpolledCaches, threadsafe.CacheAvailableStatus) {
+) (threadsafe.ResultInfoHistory, threadsafe.ResultStatHistory, threadsafe.CacheKbpses, threadsafe.DurationMap, threadsafe.LastStats, threadsafe.DSStatsReader, threadsafe.UnpolledCaches, threadsafe.CacheAvailableStatus) {
 	statInfoHistory := threadsafe.NewResultInfoHistory()
 	statResultHistory := threadsafe.NewResultStatHistory()
 	statMaxKbpses := threadsafe.NewCacheKbpses()
-	lastStatDurations := NewDurationMapThreadsafe()
+	lastStatDurations := threadsafe.NewDurationMap()
 	lastStatEndTimes := map[enum.CacheName]time.Time{}
 	lastStats := threadsafe.NewLastStats()
 	dsStats := threadsafe.NewDSStats()
@@ -136,7 +136,7 @@ func processStatResults(
 	errorCount threadsafe.Uint,
 	dsStats threadsafe.DSStats,
 	lastStatEndTimes map[enum.CacheName]time.Time,
-	lastStatDurationsThreadsafe DurationMapThreadsafe,
+	lastStatDurationsThreadsafe threadsafe.DurationMap,
 	unpolledCaches threadsafe.UnpolledCaches,
 	mc to.TrafficMonitorConfigMap,
 	precomputedData map[enum.CacheName]cache.PrecomputedData,
@@ -211,7 +211,7 @@ func processStatResults(
 	combineState()
 
 	endTime := time.Now()
-	lastStatDurations := lastStatDurationsThreadsafe.Get().Copy()
+	lastStatDurations := threadsafe.CopyDurationMap(lastStatDurationsThreadsafe.Get())
 	for _, result := range results {
 		if lastStatStart, ok := lastStatEndTimes[result.ID]; ok {
 			d := time.Since(lastStatStart)

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/threadsafe/durationmap.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/threadsafe/durationmap.go b/traffic_monitor_golang/traffic_monitor/threadsafe/durationmap.go
new file mode 100644
index 0000000..60d65c1
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/threadsafe/durationmap.go
@@ -0,0 +1,43 @@
+package threadsafe
+
+import (
+	"sync"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+)
+
+// DurationMap wraps a map[enum.CacheName]time.Duration in an object safe for a single writer and multiple readers
+type DurationMap struct {
+	durationMap *map[enum.CacheName]time.Duration
+	m           *sync.RWMutex
+}
+
+// Copy copies this duration map.
+func CopyDurationMap(a map[enum.CacheName]time.Duration) map[enum.CacheName]time.Duration {
+	b := map[enum.CacheName]time.Duration{}
+	for k, v := range a {
+		b[k] = v
+	}
+	return b
+}
+
+// NewDurationMap returns a new DurationMap safe for multiple readers and a single writer goroutine.
+func NewDurationMap() DurationMap {
+	m := map[enum.CacheName]time.Duration{}
+	return DurationMap{m: &sync.RWMutex{}, durationMap: &m}
+}
+
+// Get returns the duration map. Callers MUST NOT mutate. If mutation is necessary, call DurationMap.Copy().
+func (o *DurationMap) Get() map[enum.CacheName]time.Duration {
+	o.m.RLock()
+	defer o.m.RUnlock()
+	return *o.durationMap
+}
+
+// Set sets the internal duration map. This MUST NOT be called by multiple goroutines.
+func (o *DurationMap) Set(d map[enum.CacheName]time.Duration) {
+	o.m.Lock()
+	*o.durationMap = d
+	o.m.Unlock()
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/threadsafe/monitorconfig.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/threadsafe/monitorconfig.go b/traffic_monitor_golang/traffic_monitor/threadsafe/monitorconfig.go
new file mode 100644
index 0000000..3cb3fa9
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/threadsafe/monitorconfig.go
@@ -0,0 +1,62 @@
+package threadsafe
+
+import (
+	"sync"
+
+	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
+)
+
+// CopyTrafficMonitorConfigMap returns a deep copy of the given TrafficMonitorConfigMap
+func CopyTrafficMonitorConfigMap(a *to.TrafficMonitorConfigMap) to.TrafficMonitorConfigMap {
+	b := to.TrafficMonitorConfigMap{}
+	b.TrafficServer = map[string]to.TrafficServer{}
+	b.CacheGroup = map[string]to.TMCacheGroup{}
+	b.Config = map[string]interface{}{}
+	b.TrafficMonitor = map[string]to.TrafficMonitor{}
+	b.DeliveryService = map[string]to.TMDeliveryService{}
+	b.Profile = map[string]to.TMProfile{}
+	for k, v := range a.TrafficServer {
+		b.TrafficServer[k] = v
+	}
+	for k, v := range a.CacheGroup {
+		b.CacheGroup[k] = v
+	}
+	for k, v := range a.Config {
+		b.Config[k] = v
+	}
+	for k, v := range a.TrafficMonitor {
+		b.TrafficMonitor[k] = v
+	}
+	for k, v := range a.DeliveryService {
+		b.DeliveryService[k] = v
+	}
+	for k, v := range a.Profile {
+		b.Profile[k] = v
+	}
+	return b
+}
+
+// TrafficMonitorConfigMapThreadsafe encapsulates a TrafficMonitorConfigMap safe for multiple readers and a single writer.
+type TrafficMonitorConfigMap struct {
+	monitorConfig *to.TrafficMonitorConfigMap
+	m             *sync.RWMutex
+}
+
+// NewTrafficMonitorConfigMap returns an encapsulated TrafficMonitorConfigMap safe for multiple readers and a single writer.
+func NewTrafficMonitorConfigMap() TrafficMonitorConfigMap {
+	return TrafficMonitorConfigMap{monitorConfig: &to.TrafficMonitorConfigMap{}, m: &sync.RWMutex{}}
+}
+
+// Get returns the TrafficMonitorConfigMap. Callers MUST NOT modify, it is not threadsafe for mutation. If mutation is necessary, call CopyTrafficMonitorConfigMap().
+func (t *TrafficMonitorConfigMap) Get() to.TrafficMonitorConfigMap {
+	t.m.RLock()
+	defer t.m.RUnlock()
+	return *t.monitorConfig
+}
+
+// Set sets the TrafficMonitorConfigMap. This is only safe for one writer. This MUST NOT be called by multiple threads.
+func (t *TrafficMonitorConfigMap) Set(c to.TrafficMonitorConfigMap) {
+	t.m.Lock()
+	*t.monitorConfig = c
+	t.m.Unlock()
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/threadsafe/opsconfig.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/threadsafe/opsconfig.go b/traffic_monitor_golang/traffic_monitor/threadsafe/opsconfig.go
new file mode 100644
index 0000000..29a5fcf
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/threadsafe/opsconfig.go
@@ -0,0 +1,33 @@
+package threadsafe
+
+import (
+	"sync"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/handler"
+)
+
+// OpsConfig provides safe access for multiple reader goroutines and a single writer to a stored OpsConfig object.
+// This could be made lock-free, if the performance was necessary
+type OpsConfig struct {
+	opsConfig *handler.OpsConfig
+	m         *sync.RWMutex
+}
+
+// NewOpsConfig returns a new single-writer-multiple-reader OpsConfig
+func NewOpsConfig() OpsConfig {
+	return OpsConfig{m: &sync.RWMutex{}, opsConfig: &handler.OpsConfig{}}
+}
+
+// Get gets the internal OpsConfig object. This MUST NOT be modified. If modification is necessary, copy the object.
+func (o *OpsConfig) Get() handler.OpsConfig {
+	o.m.RLock()
+	defer o.m.RUnlock()
+	return *o.opsConfig
+}
+
+// Set sets the internal OpsConfig object. This MUST NOT be called from multiple goroutines.
+func (o *OpsConfig) Set(newOpsConfig handler.OpsConfig) {
+	o.m.Lock()
+	*o.opsConfig = newOpsConfig
+	o.m.Unlock()
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/traffic_monitor.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/traffic_monitor.go b/traffic_monitor_golang/traffic_monitor/traffic_monitor.go
index 952cc0c..032b339 100644
--- a/traffic_monitor_golang/traffic_monitor/traffic_monitor.go
+++ b/traffic_monitor_golang/traffic_monitor/traffic_monitor.go
@@ -20,17 +20,12 @@ package main
  */
 
 import (
-	"bytes"
 	"flag"
 	"fmt"
 	"io"
 	"io/ioutil"
-	"math"
 	"os"
-	"os/exec"
-	"path/filepath"
 	"runtime"
-	"time"
 
 	_ "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/instrumentation"
 	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
@@ -45,46 +40,6 @@ var GitRevision = "No Git Revision Specified. Please build with '-X main.GitRevi
 // BuildTimestamp is the time the app was built. The app SHOULD always be built with this set via the `-X` flag.
 var BuildTimestamp = "No Build Timestamp Specified. Please build with '-X main.BuildTimestamp=`date +'%Y-%M-%dT%H:%M:%S'`"
 
-// getHostNameWithoutDomain returns the machine hostname, without domain information.
-// Modified from http://stackoverflow.com/a/34331660/292623
-func getHostNameWithoutDomain() (string, error) {
-	cmd := exec.Command("/bin/hostname", "-s")
-	var out bytes.Buffer
-	cmd.Stdout = &out
-	err := cmd.Run()
-	if err != nil {
-		return "", err
-	}
-	hostname := out.String()
-	if len(hostname) < 1 {
-		return "", fmt.Errorf("OS returned empty hostname")
-	}
-	hostname = hostname[:len(hostname)-1] // removing EOL
-	return hostname, nil
-}
-
-// getStaticAppData returns app data available at start time.
-// This should be called immediately, as it includes calculating when the app was started.
-func getStaticAppData() (manager.StaticAppData, error) {
-	var d manager.StaticAppData
-	var err error
-	d.StartTime = time.Now()
-	d.GitRevision = GitRevision
-	d.FreeMemoryMB = math.MaxUint64 // TODO remove if/when nothing needs this
-	d.Version = Version
-	if d.WorkingDir, err = os.Getwd(); err != nil {
-		return manager.StaticAppData{}, err
-	}
-	d.Name = os.Args[0]
-	d.BuildTimestamp = BuildTimestamp
-	if d.Hostname, err = getHostNameWithoutDomain(); err != nil {
-		return manager.StaticAppData{}, err
-	}
-
-	d.UserAgent = fmt.Sprintf("%s/%s", filepath.Base(d.Name), d.Version)
-	return d, nil
-}
-
 func getLogWriter(location string) (io.Writer, error) {
 	switch location {
 	case config.LogLocationStdout:
@@ -124,7 +79,7 @@ func getLogWriters(eventLoc, errLoc, warnLoc, infoLoc, debugLoc string) (io.Writ
 func main() {
 	runtime.GOMAXPROCS(runtime.NumCPU())
 
-	staticData, err := getStaticAppData()
+	staticData, err := config.GetStaticAppData(Version, GitRevision, BuildTimestamp)
 	if err != nil {
 		fmt.Printf("Error starting service: failed to get static app data: %v\n", err)
 		os.Exit(1)


[2/3] incubator-trafficcontrol git commit: Move TM2 datarequest to its own package

Posted by ne...@apache.org.
Move TM2 datarequest to its own package

This breaks up a 1200-line file, into one file per endpoint.


Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/aca1457e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/aca1457e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/aca1457e

Branch: refs/heads/master
Commit: aca1457efb0e12b6148dd995ae73f934e3a32fe7
Parents: fe50d09
Author: Robert Butts <ro...@gmail.com>
Authored: Tue Feb 28 13:44:35 2017 -0700
Committer: David Neuman <da...@gmail.com>
Committed: Wed Mar 1 20:52:36 2017 -0700

----------------------------------------------------------------------
 traffic_monitor_golang/common/util/num.go       |    2 +
 .../traffic_monitor/config/staticappdata.go     |   64 +
 .../traffic_monitor/datareq/bandwidth.go        |   18 +
 .../datareq/bandwidthcapacity.go                |   16 +
 .../datareq/cacheavailablecount.go              |   28 +
 .../traffic_monitor/datareq/cachecount.go       |   12 +
 .../traffic_monitor/datareq/cachedowncount.go   |   25 +
 .../traffic_monitor/datareq/cachestat.go        |   21 +
 .../traffic_monitor/datareq/cachestate.go       |  277 ++++
 .../traffic_monitor/datareq/cachestatfilter.go  |  149 ++
 .../traffic_monitor/datareq/configdoc.go        |   16 +
 .../traffic_monitor/datareq/crconfig.go         |   20 +
 .../traffic_monitor/datareq/crstate.go          |   22 +
 .../traffic_monitor/datareq/datareq.go          |  235 ++++
 .../traffic_monitor/datareq/dsstat.go           |   20 +
 .../traffic_monitor/datareq/dsstatfilter.go     |  137 ++
 .../traffic_monitor/datareq/eventlog.go         |   16 +
 .../traffic_monitor/datareq/monitorconfig.go    |   11 +
 .../traffic_monitor/datareq/peerstate.go        |   60 +
 .../traffic_monitor/datareq/peerstatefilter.go  |  139 ++
 .../traffic_monitor/datareq/stat.go             |  143 ++
 .../traffic_monitor/datareq/statsummary.go      |   98 ++
 .../traffic_monitor/datareq/trafficopsuri.go    |    9 +
 .../traffic_monitor/datareq/version.go          |   15 +
 .../traffic_monitor/manager/datarequest.go      | 1295 ------------------
 .../traffic_monitor/manager/health.go           |   55 +-
 .../traffic_monitor/manager/manager.go          |   16 +-
 .../traffic_monitor/manager/monitorconfig.go    |   67 +-
 .../traffic_monitor/manager/opsconfig.go        |   40 +-
 .../traffic_monitor/manager/stat.go             |   12 +-
 .../traffic_monitor/threadsafe/durationmap.go   |   43 +
 .../traffic_monitor/threadsafe/monitorconfig.go |   62 +
 .../traffic_monitor/threadsafe/opsconfig.go     |   33 +
 .../traffic_monitor/traffic_monitor.go          |   47 +-
 34 files changed, 1720 insertions(+), 1503 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/common/util/num.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/common/util/num.go b/traffic_monitor_golang/common/util/num.go
index 4db2cf9..e15a129 100644
--- a/traffic_monitor_golang/common/util/num.go
+++ b/traffic_monitor_golang/common/util/num.go
@@ -1,5 +1,7 @@
 package util
 
+const MillisecondsPerNanosecond = int64(1000000)
+
 // ToNumeric returns a float for any numeric type, and false if the interface does not hold a numeric type.
 // This allows converting unknown numeric types (for example, from JSON) in a single line
 // TODO try to parse string stats as numbers?

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/config/staticappdata.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/config/staticappdata.go b/traffic_monitor_golang/traffic_monitor/config/staticappdata.go
new file mode 100644
index 0000000..feb442d
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/config/staticappdata.go
@@ -0,0 +1,64 @@
+package config
+
+import (
+	"bytes"
+	"fmt"
+	"math"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"time"
+)
+
+// StaticAppData encapsulates data about the app available at startup
+type StaticAppData struct {
+	StartTime      time.Time
+	GitRevision    string
+	FreeMemoryMB   uint64
+	Version        string
+	WorkingDir     string
+	Name           string
+	BuildTimestamp string
+	Hostname       string
+	UserAgent      string
+}
+
+// getStaticAppData returns app data available at start time.
+// This should be called immediately, as it includes calculating when the app was started.
+func GetStaticAppData(version, gitRevision, buildTimestamp string) (StaticAppData, error) {
+	var d StaticAppData
+	var err error
+	d.StartTime = time.Now()
+	d.GitRevision = gitRevision
+	d.FreeMemoryMB = math.MaxUint64 // TODO remove if/when nothing needs this
+	d.Version = version
+	if d.WorkingDir, err = os.Getwd(); err != nil {
+		return StaticAppData{}, err
+	}
+	d.Name = os.Args[0]
+	d.BuildTimestamp = buildTimestamp
+	if d.Hostname, err = getHostNameWithoutDomain(); err != nil {
+		return StaticAppData{}, err
+	}
+
+	d.UserAgent = fmt.Sprintf("%s/%s", filepath.Base(d.Name), d.Version)
+	return d, nil
+}
+
+// getHostNameWithoutDomain returns the machine hostname, without domain information.
+// Modified from http://stackoverflow.com/a/34331660/292623
+func getHostNameWithoutDomain() (string, error) {
+	cmd := exec.Command("/bin/hostname", "-s")
+	var out bytes.Buffer
+	cmd.Stdout = &out
+	err := cmd.Run()
+	if err != nil {
+		return "", err
+	}
+	hostname := out.String()
+	if len(hostname) < 1 {
+		return "", fmt.Errorf("OS returned empty hostname")
+	}
+	hostname = hostname[:len(hostname)-1] // removing EOL
+	return hostname, nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/bandwidth.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/bandwidth.go b/traffic_monitor_golang/traffic_monitor/datareq/bandwidth.go
new file mode 100644
index 0000000..3306004
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/bandwidth.go
@@ -0,0 +1,18 @@
+package datareq
+
+import (
+	"fmt"
+
+	ds "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/deliveryservice"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
+	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
+)
+
+func srvAPIBandwidthKbps(toData todata.TODataThreadsafe, lastStats threadsafe.LastStats) []byte {
+	kbpsStats := lastStats.Get()
+	sum := float64(0.0)
+	for _, data := range kbpsStats.Caches {
+		sum += data.Bytes.PerSec / ds.BytesPerKilobit
+	}
+	return []byte(fmt.Sprintf("%f", sum))
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/bandwidthcapacity.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/bandwidthcapacity.go b/traffic_monitor_golang/traffic_monitor/datareq/bandwidthcapacity.go
new file mode 100644
index 0000000..c044b29
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/bandwidthcapacity.go
@@ -0,0 +1,16 @@
+package datareq
+
+import (
+	"fmt"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
+)
+
+func srvAPIBandwidthCapacityKbps(statMaxKbpses threadsafe.CacheKbpses) []byte {
+	maxKbpses := statMaxKbpses.Get()
+	cap := int64(0)
+	for _, kbps := range maxKbpses {
+		cap += kbps
+	}
+	return []byte(fmt.Sprintf("%d", cap))
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/cacheavailablecount.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/cacheavailablecount.go b/traffic_monitor_golang/traffic_monitor/datareq/cacheavailablecount.go
new file mode 100644
index 0000000..be838b5
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/cacheavailablecount.go
@@ -0,0 +1,28 @@
+package datareq
+
+import (
+	"strconv"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
+)
+
+func srvAPICacheAvailableCount(localStates peer.CRStatesThreadsafe) []byte {
+	return []byte(strconv.Itoa(cacheAvailableCount(localStates.Get().Caches)))
+}
+
+// cacheOfflineCount returns the total caches not available, including marked unavailable, status offline, and status admin_down
+func cacheOfflineCount(caches map[enum.CacheName]peer.IsAvailable) int {
+	count := 0
+	for _, available := range caches {
+		if !available.IsAvailable {
+			count++
+		}
+	}
+	return count
+}
+
+// cacheAvailableCount returns the total caches available, including marked available and status online
+func cacheAvailableCount(caches map[enum.CacheName]peer.IsAvailable) int {
+	return len(caches) - cacheOfflineCount(caches)
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/cachecount.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/cachecount.go b/traffic_monitor_golang/traffic_monitor/datareq/cachecount.go
new file mode 100644
index 0000000..c25b791
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/cachecount.go
@@ -0,0 +1,12 @@
+package datareq
+
+import (
+	"strconv"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
+)
+
+// TODO determine if this should use peerStates
+func srvAPICacheCount(localStates peer.CRStatesThreadsafe) []byte {
+	return []byte(strconv.Itoa(len(localStates.Get().Caches)))
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/cachedowncount.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/cachedowncount.go b/traffic_monitor_golang/traffic_monitor/datareq/cachedowncount.go
new file mode 100644
index 0000000..321f724
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/cachedowncount.go
@@ -0,0 +1,25 @@
+package datareq
+
+import (
+	"strconv"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
+	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
+)
+
+func srvAPICacheDownCount(localStates peer.CRStatesThreadsafe, monitorConfig threadsafe.TrafficMonitorConfigMap) []byte {
+	return []byte(strconv.Itoa(cacheDownCount(localStates.Get().Caches, monitorConfig.Get().TrafficServer)))
+}
+
+// cacheOfflineCount returns the total reported caches marked down, excluding status offline and admin_down.
+func cacheDownCount(caches map[enum.CacheName]peer.IsAvailable, toServers map[string]to.TrafficServer) int {
+	count := 0
+	for cache, available := range caches {
+		if !available.IsAvailable && enum.CacheStatusFromString(toServers[string(cache)].Status) == enum.CacheStatusReported {
+			count++
+		}
+	}
+	return count
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/cachestat.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/cachestat.go b/traffic_monitor_golang/traffic_monitor/datareq/cachestat.go
new file mode 100644
index 0000000..fbcd369
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/cachestat.go
@@ -0,0 +1,21 @@
+package datareq
+
+import (
+	"net/http"
+	"net/url"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/cache"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
+	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
+)
+
+func srvCacheStats(params url.Values, errorCount threadsafe.Uint, path string, toData todata.TODataThreadsafe, statResultHistory threadsafe.ResultStatHistory, statInfoHistory threadsafe.ResultInfoHistory, monitorConfig threadsafe.TrafficMonitorConfigMap, combinedStates peer.CRStatesThreadsafe, statMaxKbpses threadsafe.CacheKbpses) ([]byte, int) {
+	filter, err := NewCacheStatFilter(path, params, toData.Get().ServerTypes)
+	if err != nil {
+		HandleErr(errorCount, path, err)
+		return []byte(err.Error()), http.StatusBadRequest
+	}
+	bytes, err := cache.StatsMarshall(statResultHistory.Get(), statInfoHistory.Get(), combinedStates.Get(), monitorConfig.Get(), statMaxKbpses.Get(), filter, params)
+	return WrapErrCode(errorCount, path, bytes, err)
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/cachestate.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/cachestate.go b/traffic_monitor_golang/traffic_monitor/datareq/cachestate.go
new file mode 100644
index 0000000..871be49
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/cachestate.go
@@ -0,0 +1,277 @@
+package datareq
+
+import (
+	"encoding/json"
+	"fmt"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/cache"
+	ds "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/deliveryservice"
+	dsdata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/deliveryservicedata"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
+	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
+)
+
+// CacheStatus contains summary stat data about the given cache.
+// TODO make fields nullable, so error fields can be omitted, letting API callers still get updates for unerrored fields
+type CacheStatus struct {
+	Type         *string  `json:"type,omitempty"`
+	Status       *string  `json:"status,omitempty"`
+	StatusPoller *string  `json:"status_poller,omitempty"`
+	LoadAverage  *float64 `json:"load_average,omitempty"`
+	// QueryTimeMilliseconds is the time it took this app to perform a complete query and process the data, end-to-end, for the latest health query.
+	QueryTimeMilliseconds *int64 `json:"query_time_ms,omitempty"`
+	// HealthTimeMilliseconds is the time it took to make the HTTP request and get back the full response, for the latest health query.
+	HealthTimeMilliseconds *int64 `json:"health_time_ms,omitempty"`
+	// StatTimeMilliseconds is the time it took to make the HTTP request and get back the full response, for the latest stat query.
+	StatTimeMilliseconds *int64 `json:"stat_time_ms,omitempty"`
+	// StatSpanMilliseconds is the length of time between completing the most recent two stat queries. This can be used as a rough gauge of the end-to-end query processing time.
+	StatSpanMilliseconds *int64 `json:"stat_span_ms,omitempty"`
+	// HealthSpanMilliseconds is the length of time between completing the most recent two health queries. This can be used as a rough gauge of the end-to-end query processing time.
+	HealthSpanMilliseconds *int64   `json:"health_span_ms,omitempty"`
+	BandwidthKbps          *float64 `json:"bandwidth_kbps,omitempty"`
+	BandwidthCapacityKbps  *float64 `json:"bandwidth_capacity_kbps,omitempty"`
+	ConnectionCount        *int64   `json:"connection_count,omitempty"`
+}
+
+func srvAPICacheStates(toData todata.TODataThreadsafe, statInfoHistory threadsafe.ResultInfoHistory, statResultHistory threadsafe.ResultStatHistory, healthHistory threadsafe.ResultHistory, lastHealthDurations threadsafe.DurationMap, localStates peer.CRStatesThreadsafe, lastStats threadsafe.LastStats, localCacheStatus threadsafe.CacheAvailableStatus, statMaxKbpses threadsafe.CacheKbpses) ([]byte, error) {
+	return json.Marshal(createCacheStatuses(toData.Get().ServerTypes, statInfoHistory.Get(), statResultHistory.Get(), healthHistory.Get(), lastHealthDurations.Get(), localStates.Get().Caches, lastStats.Get(), localCacheStatus, statMaxKbpses))
+}
+
+func createCacheStatuses(
+	cacheTypes map[enum.CacheName]enum.CacheType,
+	statInfoHistory cache.ResultInfoHistory,
+	statResultHistory cache.ResultStatHistory,
+	healthHistory map[enum.CacheName][]cache.Result,
+	lastHealthDurations map[enum.CacheName]time.Duration,
+	cacheStates map[enum.CacheName]peer.IsAvailable,
+	lastStats dsdata.LastStats,
+	localCacheStatusThreadsafe threadsafe.CacheAvailableStatus,
+	statMaxKbpses threadsafe.CacheKbpses,
+) map[enum.CacheName]CacheStatus {
+	conns := createCacheConnections(statResultHistory)
+	statii := map[enum.CacheName]CacheStatus{}
+	localCacheStatus := localCacheStatusThreadsafe.Get().Copy() // TODO test whether copy is necessary
+	maxKbpses := statMaxKbpses.Get()
+
+	for cacheName, cacheType := range cacheTypes {
+		infoHistory, ok := statInfoHistory[cacheName]
+		if !ok {
+			log.Infof("createCacheStatuses stat info history missing cache %s\n", cacheName)
+			continue
+		}
+
+		if len(infoHistory) < 1 {
+			log.Infof("createCacheStatuses stat info history empty for cache %s\n", cacheName)
+			continue
+		}
+
+		log.Debugf("createCacheStatuses NOT empty for cache %s\n", cacheName)
+
+		loadAverage := &infoHistory[0].Vitals.LoadAvg
+
+		healthQueryTime, err := latestQueryTimeMS(cacheName, lastHealthDurations)
+		if err != nil {
+			log.Infof("Error getting cache %v health query time: %v\n", cacheName, err)
+		}
+
+		statTime, err := latestResultInfoTimeMS(cacheName, statInfoHistory)
+		if err != nil {
+			log.Infof("Error getting cache %v stat result time: %v\n", cacheName, err)
+		}
+
+		healthTime, err := latestResultTimeMS(cacheName, healthHistory)
+		if err != nil {
+			log.Infof("Error getting cache %v health result time: %v\n", cacheName, err)
+		}
+
+		statSpan, err := infoResultSpanMS(cacheName, statInfoHistory)
+		if err != nil {
+			log.Infof("Error getting cache %v stat span: %v\n", cacheName, err)
+		}
+
+		healthSpan, err := resultSpanMS(cacheName, healthHistory)
+		if err != nil {
+			log.Infof("Error getting cache %v health span: %v\n", cacheName, err)
+		}
+
+		var kbps *float64
+		if lastStat, ok := lastStats.Caches[cacheName]; !ok {
+			log.Infof("cache not in last kbps cache %s\n", cacheName)
+		} else {
+			kbpsVal := lastStat.Bytes.PerSec / float64(ds.BytesPerKilobit)
+			kbps = &kbpsVal
+		}
+
+		var maxKbps *float64
+		if v, ok := maxKbpses[cacheName]; !ok {
+			log.Infof("cache not in max kbps cache %s\n", cacheName)
+		} else {
+			fv := float64(v)
+			maxKbps = &fv
+		}
+
+		var connections *int64
+		connectionsVal, ok := conns[cacheName]
+		if !ok {
+			log.Infof("cache not in connections %s\n", cacheName)
+		} else {
+			connections = &connectionsVal
+		}
+
+		var status *string
+		var statusPoller *string
+		statusVal, ok := localCacheStatus[cacheName]
+		if !ok {
+			log.Infof("cache not in statuses %s\n", cacheName)
+		} else {
+			statusString := statusVal.Status + " - "
+
+			// this should match the event string, use as the default if possible
+			if statusVal.Why != "" {
+				statusString = statusVal.Why
+			} else if statusVal.Available {
+				statusString += "available"
+			} else {
+				statusString += fmt.Sprintf("unavailable (%s)", statusVal.Why)
+			}
+
+			status = &statusString
+			statusPoller = &statusVal.Poller
+		}
+
+		cacheTypeStr := string(cacheType)
+		statii[cacheName] = CacheStatus{
+			Type:                   &cacheTypeStr,
+			LoadAverage:            loadAverage,
+			QueryTimeMilliseconds:  &healthQueryTime,
+			StatTimeMilliseconds:   &statTime,
+			HealthTimeMilliseconds: &healthTime,
+			StatSpanMilliseconds:   &statSpan,
+			HealthSpanMilliseconds: &healthSpan,
+			BandwidthKbps:          kbps,
+			BandwidthCapacityKbps:  maxKbps,
+			ConnectionCount:        connections,
+			Status:                 status,
+			StatusPoller:           statusPoller,
+		}
+	}
+	return statii
+}
+
+func createCacheConnections(statResultHistory cache.ResultStatHistory) map[enum.CacheName]int64 {
+	conns := map[enum.CacheName]int64{}
+	for server, history := range statResultHistory {
+		vals, ok := history["proxy.process.http.current_client_connections"]
+		if !ok || len(vals) < 1 {
+			continue
+		}
+
+		v, ok := vals[0].Val.(float64)
+		if !ok {
+			continue // TODO log warning? error?
+		}
+		conns[server] = int64(v)
+	}
+	return conns
+}
+
+// infoResultSpanMS returns the length of time between the most recent two results. That is, how long could the cache have been down before we would have noticed it? Note this returns the time between the most recent two results, irrespective if they errored.
+// Note this is unrelated to the Stat Span field.
+func infoResultSpanMS(cacheName enum.CacheName, history cache.ResultInfoHistory) (int64, error) {
+	results, ok := history[cacheName]
+	if !ok {
+		return 0, fmt.Errorf("cache %v has no history", cacheName)
+	}
+	if len(results) == 0 {
+		return 0, fmt.Errorf("cache %v history empty", cacheName)
+	}
+	if len(results) < 2 {
+		return 0, fmt.Errorf("cache %v history only has one result, can't compute span between results", cacheName)
+	}
+
+	latestResult := results[0]
+	penultimateResult := results[1]
+	span := latestResult.Time.Sub(penultimateResult.Time)
+	return int64(span / time.Millisecond), nil
+}
+
+// resultSpanMS returns the length of time between the most recent two results. That is, how long could the cache have been down before we would have noticed it? Note this returns the time between the most recent two results, irrespective if they errored.
+// Note this is unrelated to the Stat Span field.
+func resultSpanMS(cacheName enum.CacheName, history map[enum.CacheName][]cache.Result) (int64, error) {
+	results, ok := history[cacheName]
+	if !ok {
+		return 0, fmt.Errorf("cache %v has no history", cacheName)
+	}
+	if len(results) == 0 {
+		return 0, fmt.Errorf("cache %v history empty", cacheName)
+	}
+	if len(results) < 2 {
+		return 0, fmt.Errorf("cache %v history only has one result, can't compute span between results", cacheName)
+	}
+
+	latestResult := results[0]
+	penultimateResult := results[1]
+	span := latestResult.Time.Sub(penultimateResult.Time)
+	return int64(span / time.Millisecond), nil
+}
+
+func latestQueryTimeMS(cacheName enum.CacheName, lastDurations map[enum.CacheName]time.Duration) (int64, error) {
+	queryTime, ok := lastDurations[cacheName]
+	if !ok {
+		return 0, fmt.Errorf("cache %v not in last durations\n", cacheName)
+	}
+	return int64(queryTime / time.Millisecond), nil
+}
+
+// latestResultTimeMS returns the length of time in milliseconds that it took to request the most recent non-errored result.
+func latestResultTimeMS(cacheName enum.CacheName, history map[enum.CacheName][]cache.Result) (int64, error) {
+
+	results, ok := history[cacheName]
+	if !ok {
+		return 0, fmt.Errorf("cache %v has no history", cacheName)
+	}
+	if len(results) == 0 {
+		return 0, fmt.Errorf("cache %v history empty", cacheName)
+	}
+	result := cache.Result{}
+	foundResult := false
+	for _, r := range results {
+		if r.Error == nil {
+			result = r
+			foundResult = true
+			break
+		}
+	}
+	if !foundResult {
+		return 0, fmt.Errorf("cache %v No unerrored result", cacheName)
+	}
+	return int64(result.RequestTime / time.Millisecond), nil
+}
+
+// latestResultInfoTimeMS returns the length of time in milliseconds that it took to request the most recent non-errored result info.
+func latestResultInfoTimeMS(cacheName enum.CacheName, history cache.ResultInfoHistory) (int64, error) {
+	results, ok := history[cacheName]
+	if !ok {
+		return 0, fmt.Errorf("cache %v has no history", cacheName)
+	}
+	if len(results) == 0 {
+		return 0, fmt.Errorf("cache %v history empty", cacheName)
+	}
+	result := cache.ResultInfo{}
+	foundResult := false
+	for _, r := range results {
+		if r.Error == nil {
+			result = r
+			foundResult = true
+			break
+		}
+	}
+	if !foundResult {
+		return 0, fmt.Errorf("cache %v No unerrored result", cacheName)
+	}
+	return int64(result.RequestTime / time.Millisecond), nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/cachestatfilter.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/cachestatfilter.go b/traffic_monitor_golang/traffic_monitor/datareq/cachestatfilter.go
new file mode 100644
index 0000000..6e9a764
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/cachestatfilter.go
@@ -0,0 +1,149 @@
+package datareq
+
+import (
+	"fmt"
+	"net/url"
+	"strconv"
+	"strings"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/cache"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+)
+
+// CacheStatFilter fulfills the cache.Filter interface, for filtering stats. See the `NewCacheStatFilter` documentation for details on which query parameters are used to filter.
+type CacheStatFilter struct {
+	historyCount int
+	statsToUse   map[string]struct{}
+	wildcard     bool
+	cacheType    enum.CacheType
+	hosts        map[enum.CacheName]struct{}
+	cacheTypes   map[enum.CacheName]enum.CacheType
+}
+
+// UseCache returns whether the given cache is in the filter.
+func (f *CacheStatFilter) UseCache(name enum.CacheName) bool {
+	if _, inHosts := f.hosts[name]; len(f.hosts) != 0 && !inHosts {
+		return false
+	}
+	if f.cacheType != enum.CacheTypeInvalid && f.cacheTypes[name] != f.cacheType {
+		return false
+	}
+	return true
+}
+
+// UseStat returns whether the given stat is in the filter.
+func (f *CacheStatFilter) UseStat(statName string) bool {
+	if len(f.statsToUse) == 0 {
+		return true
+	}
+	if !f.wildcard {
+		_, ok := f.statsToUse[statName]
+		return ok
+	}
+	for statToUse := range f.statsToUse {
+		if strings.Contains(statName, statToUse) {
+			return true
+		}
+	}
+	return false
+}
+
+// WithinStatHistoryMax returns whether the given history index is less than the max history of this filter.
+func (f *CacheStatFilter) WithinStatHistoryMax(n int) bool {
+	if f.historyCount == 0 {
+		return true
+	}
+	if n <= f.historyCount {
+		return true
+	}
+	return false
+}
+
+// NewCacheStatFilter takes the HTTP query parameters and creates a CacheStatFilter which fulfills the `cache.Filter` interface, filtering according to the query parameters passed.
+// Query parameters used are `hc`, `stats`, `wildcard`, `type`, and `hosts`.
+// If `hc` is 0, all history is returned. If `hc` is empty, 1 history is returned.
+// If `stats` is empty, all stats are returned.
+// If `wildcard` is empty, `stats` is considered exact.
+// If `type` is empty, all cache types are returned.
+func NewCacheStatFilter(path string, params url.Values, cacheTypes map[enum.CacheName]enum.CacheType) (cache.Filter, error) {
+	validParams := map[string]struct{}{
+		"hc":       struct{}{},
+		"stats":    struct{}{},
+		"wildcard": struct{}{},
+		"type":     struct{}{},
+		"hosts":    struct{}{},
+		"cache":    struct{}{},
+	}
+	if len(params) > len(validParams) {
+		return nil, fmt.Errorf("invalid query parameters")
+	}
+	for param := range params {
+		if _, ok := validParams[param]; !ok {
+			return nil, fmt.Errorf("invalid query parameter '%v'", param)
+		}
+	}
+
+	historyCount := 1
+	if paramHc, exists := params["hc"]; exists && len(paramHc) > 0 {
+		v, err := strconv.Atoi(paramHc[0])
+		if err == nil {
+			historyCount = v
+		}
+	}
+
+	statsToUse := map[string]struct{}{}
+	if paramStats, exists := params["stats"]; exists && len(paramStats) > 0 {
+		commaStats := strings.Split(paramStats[0], ",")
+		for _, stat := range commaStats {
+			statsToUse[stat] = struct{}{}
+		}
+	}
+
+	wildcard := false
+	if paramWildcard, exists := params["wildcard"]; exists && len(paramWildcard) > 0 {
+		wildcard, _ = strconv.ParseBool(paramWildcard[0]) // ignore errors, error => false
+	}
+
+	cacheType := enum.CacheTypeInvalid
+	if paramType, exists := params["type"]; exists && len(paramType) > 0 {
+		cacheType = enum.CacheTypeFromString(paramType[0])
+		if cacheType == enum.CacheTypeInvalid {
+			return nil, fmt.Errorf("invalid query parameter type '%v' - valid types are: {edge, mid}", paramType[0])
+		}
+	}
+
+	hosts := map[enum.CacheName]struct{}{}
+	if paramHosts, exists := params["hosts"]; exists && len(paramHosts) > 0 {
+		commaHosts := strings.Split(paramHosts[0], ",")
+		for _, host := range commaHosts {
+			hosts[enum.CacheName(host)] = struct{}{}
+		}
+	}
+	if paramHosts, exists := params["cache"]; exists && len(paramHosts) > 0 {
+		commaHosts := strings.Split(paramHosts[0], ",")
+		for _, host := range commaHosts {
+			hosts[enum.CacheName(host)] = struct{}{}
+		}
+	}
+
+	pathArgument := getPathArgument(path)
+	if pathArgument != "" {
+		hosts[enum.CacheName(pathArgument)] = struct{}{}
+	}
+
+	// parameters without values are considered hosts, e.g. `?my-cache-0`
+	for maybeHost, val := range params {
+		if len(val) == 0 || (len(val) == 1 && val[0] == "") {
+			hosts[enum.CacheName(maybeHost)] = struct{}{}
+		}
+	}
+
+	return &CacheStatFilter{
+		historyCount: historyCount,
+		statsToUse:   statsToUse,
+		wildcard:     wildcard,
+		cacheType:    cacheType,
+		hosts:        hosts,
+		cacheTypes:   cacheTypes,
+	}, nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/configdoc.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/configdoc.go b/traffic_monitor_golang/traffic_monitor/datareq/configdoc.go
new file mode 100644
index 0000000..0501b9f
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/configdoc.go
@@ -0,0 +1,16 @@
+package datareq
+
+import (
+	"encoding/json"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
+)
+
+func srvConfigDoc(opsConfig threadsafe.OpsConfig) ([]byte, error) {
+	opsConfigCopy := opsConfig.Get()
+	// if the password is blank, leave it blank, so callers can see it's missing.
+	if opsConfigCopy.Password != "" {
+		opsConfigCopy.Password = "*****"
+	}
+	return json.Marshal(opsConfigCopy)
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/crconfig.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/crconfig.go b/traffic_monitor_golang/traffic_monitor/datareq/crconfig.go
new file mode 100644
index 0000000..e8cbaac
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/crconfig.go
@@ -0,0 +1,20 @@
+package datareq
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
+	towrap "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopswrapper"
+)
+
+func srvTRConfig(opsConfig threadsafe.OpsConfig, toSession towrap.ITrafficOpsSession) ([]byte, time.Time, error) {
+	cdnName := opsConfig.Get().CdnName
+	if toSession == nil {
+		return nil, time.Time{}, fmt.Errorf("Unable to connect to Traffic Ops")
+	}
+	if cdnName == "" {
+		return nil, time.Time{}, fmt.Errorf("No CDN Configured")
+	}
+	return toSession.LastCRConfig(cdnName)
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/crstate.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/crstate.go b/traffic_monitor_golang/traffic_monitor/datareq/crstate.go
new file mode 100644
index 0000000..f672207
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/crstate.go
@@ -0,0 +1,22 @@
+package datareq
+
+import (
+	"net/url"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
+)
+
+func srvTRState(params url.Values, localStates peer.CRStatesThreadsafe, combinedStates peer.CRStatesThreadsafe) ([]byte, error) {
+	if _, raw := params["raw"]; raw {
+		return srvTRStateSelf(localStates)
+	}
+	return srvTRStateDerived(combinedStates)
+}
+
+func srvTRStateDerived(combinedStates peer.CRStatesThreadsafe) ([]byte, error) {
+	return peer.CrstatesMarshall(combinedStates.Get())
+}
+
+func srvTRStateSelf(localStates peer.CRStatesThreadsafe) ([]byte, error) {
+	return peer.CrstatesMarshall(localStates.Get())
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/datareq.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/datareq.go b/traffic_monitor_golang/traffic_monitor/datareq/datareq.go
new file mode 100644
index 0000000..d2919bb
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/datareq.go
@@ -0,0 +1,235 @@
+package datareq
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/config"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/health"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
+	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
+	towrap "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopswrapper"
+)
+
+// MakeDispatchMap returns the map of paths to http.HandlerFuncs for dispatching.
+func MakeDispatchMap(
+	opsConfig threadsafe.OpsConfig,
+	toSession towrap.ITrafficOpsSession,
+	localStates peer.CRStatesThreadsafe,
+	peerStates peer.CRStatesPeersThreadsafe,
+	combinedStates peer.CRStatesThreadsafe,
+	statInfoHistory threadsafe.ResultInfoHistory,
+	statResultHistory threadsafe.ResultStatHistory,
+	statMaxKbpses threadsafe.CacheKbpses,
+	healthHistory threadsafe.ResultHistory,
+	dsStats threadsafe.DSStatsReader,
+	events health.ThreadsafeEvents,
+	staticAppData config.StaticAppData,
+	healthPollInterval time.Duration,
+	lastHealthDurations threadsafe.DurationMap,
+	fetchCount threadsafe.Uint,
+	healthIteration threadsafe.Uint,
+	errorCount threadsafe.Uint,
+	toData todata.TODataThreadsafe,
+	localCacheStatus threadsafe.CacheAvailableStatus,
+	lastStats threadsafe.LastStats,
+	unpolledCaches threadsafe.UnpolledCaches,
+	monitorConfig threadsafe.TrafficMonitorConfigMap,
+) map[string]http.HandlerFunc {
+
+	// wrap composes all universal wrapper functions. Right now, it's only the UnpolledCheck, but there may be others later. For example, security headers.
+	wrap := func(f http.HandlerFunc) http.HandlerFunc {
+		return wrapUnpolledCheck(unpolledCaches, errorCount, f)
+	}
+
+	dispatchMap := map[string]http.HandlerFunc{
+		"/publish/CrConfig": wrap(WrapAgeErr(errorCount, func() ([]byte, time.Time, error) {
+			return srvTRConfig(opsConfig, toSession)
+		}, ContentTypeJSON)),
+		"/publish/CrStates": wrap(WrapParams(func(params url.Values, path string) ([]byte, int) {
+			bytes, err := srvTRState(params, localStates, combinedStates)
+			return WrapErrCode(errorCount, path, bytes, err)
+		}, ContentTypeJSON)),
+		"/publish/CacheStats": wrap(WrapParams(func(params url.Values, path string) ([]byte, int) {
+			return srvCacheStats(params, errorCount, path, toData, statResultHistory, statInfoHistory, monitorConfig, combinedStates, statMaxKbpses)
+		}, ContentTypeJSON)),
+		"/publish/DsStats": wrap(WrapParams(func(params url.Values, path string) ([]byte, int) {
+			return srvDSStats(params, errorCount, path, toData, dsStats)
+		}, ContentTypeJSON)),
+		"/publish/EventLog": wrap(WrapErr(errorCount, func() ([]byte, error) {
+			return srvEventLog(events)
+		}, ContentTypeJSON)),
+		"/publish/PeerStates": wrap(WrapParams(func(params url.Values, path string) ([]byte, int) {
+			return srvPeerStates(params, errorCount, path, toData, peerStates)
+		}, ContentTypeJSON)),
+		"/publish/Stats": wrap(WrapErr(errorCount, func() ([]byte, error) {
+			return srvStats(staticAppData, healthPollInterval, lastHealthDurations, fetchCount, healthIteration, errorCount, peerStates)
+		}, ContentTypeJSON)),
+		"/publish/ConfigDoc": wrap(WrapErr(errorCount, func() ([]byte, error) {
+			return srvConfigDoc(opsConfig)
+		}, ContentTypeJSON)),
+		"/publish/StatSummary": wrap(WrapParams(func(params url.Values, path string) ([]byte, int) {
+			return srvStatSummary(params, errorCount, path, toData, statResultHistory)
+		}, ContentTypeJSON)),
+		"/api/cache-count": wrap(WrapBytes(func() []byte {
+			return srvAPICacheCount(localStates)
+		}, ContentTypeJSON)),
+		"/api/cache-available-count": wrap(WrapBytes(func() []byte {
+			return srvAPICacheAvailableCount(localStates)
+		}, ContentTypeJSON)),
+		"/api/cache-down-count": wrap(WrapBytes(func() []byte {
+			return srvAPICacheDownCount(localStates, monitorConfig)
+		}, ContentTypeJSON)),
+		"/api/version": wrap(WrapBytes(func() []byte {
+			return srvAPIVersion(staticAppData)
+		}, ContentTypeJSON)),
+		"/api/traffic-ops-uri": wrap(WrapBytes(func() []byte {
+			return srvAPITrafficOpsURI(opsConfig)
+		}, ContentTypeJSON)),
+		"/api/cache-statuses": wrap(WrapErr(errorCount, func() ([]byte, error) {
+			return srvAPICacheStates(toData, statInfoHistory, statResultHistory, healthHistory, lastHealthDurations, localStates, lastStats, localCacheStatus, statMaxKbpses)
+		}, ContentTypeJSON)),
+		"/api/bandwidth-kbps": wrap(WrapBytes(func() []byte {
+			return srvAPIBandwidthKbps(toData, lastStats)
+		}, ContentTypeJSON)),
+		"/api/bandwidth-capacity-kbps": wrap(WrapBytes(func() []byte {
+			return srvAPIBandwidthCapacityKbps(statMaxKbpses)
+		}, ContentTypeJSON)),
+		"/api/monitor-config": wrap(WrapErr(errorCount, func() ([]byte, error) {
+			return srvMonitorConfig(monitorConfig)
+		}, ContentTypeJSON)),
+	}
+	return addTrailingSlashEndpoints(dispatchMap)
+}
+
+// This is the "spirit" of how TM1.0 works; hack to extract a path argument to filter data (/publish/SomeEndpoint/:argument).
+func getPathArgument(path string) string {
+	pathParts := strings.Split(path, "/")
+	if len(pathParts) >= 4 {
+		return pathParts[3]
+	}
+
+	return ""
+}
+
+// HandleErr takes an error, and the request type it came from, and logs. It is ok to call with a nil error, in which case this is a no-op.
+func HandleErr(errorCount threadsafe.Uint, reqPath string, err error) {
+	if err == nil {
+		return
+	}
+	errorCount.Inc()
+	log.Errorf("Request Error: %v\n", fmt.Errorf(reqPath+": %v", err))
+}
+
+// WrapErrCode takes the body, err, and log context (errorCount, reqPath). It logs and deals with any error, and returns the appropriate bytes and response code for the `srvhttp`. It notably returns InternalServerError status on any error, for security reasons.
+func WrapErrCode(errorCount threadsafe.Uint, reqPath string, body []byte, err error) ([]byte, int) {
+	if err == nil {
+		return body, http.StatusOK
+	}
+	HandleErr(errorCount, reqPath, err)
+	return nil, http.StatusInternalServerError
+}
+
+// WrapBytes takes a function which cannot error and returns only bytes, and wraps it as a http.HandlerFunc. The errContext is logged if the write fails, and should be enough information to trace the problem (function name, endpoint, request parameters, etc).
+func WrapBytes(f func() []byte, contentType string) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", contentType)
+		log.Write(w, f(), r.URL.EscapedPath())
+	}
+}
+
+// WrapErr takes a function which returns bytes and an error, and wraps it as a http.HandlerFunc. If the error is nil, the bytes are written with Status OK. Else, the error is logged, and InternalServerError is returned as the response code. If you need to return a different response code (for example, StatusBadRequest), call wrapRespCode.
+func WrapErr(errorCount threadsafe.Uint, f func() ([]byte, error), contentType string) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		bytes, err := f()
+		_, code := WrapErrCode(errorCount, r.URL.EscapedPath(), bytes, err)
+		w.Header().Set("Content-Type", contentType)
+		w.WriteHeader(code)
+		log.Write(w, bytes, r.URL.EscapedPath())
+	}
+}
+
+// SrvFunc is a function which takes URL parameters, and returns the requested data, and a response code. Note it does not take the full http.Request, and does not have the path. SrvFunc functions should be called via dispatch, and any additional data needed should be closed via a lambda.
+// TODO split params and path into 2 separate wrappers?
+// TODO change to simply take the http.Request?
+type SrvFunc func(params url.Values, path string) ([]byte, int)
+
+// WrapParams takes a SrvFunc and wraps it as an http.HandlerFunc. Note if the SrvFunc returns 0 bytes, an InternalServerError is returned, and the response code is ignored, for security reasons. If an error response code is necessary, return bytes to that effect, for example, "Bad Request". DO NOT return informational messages regarding internal server errors; these should be logged, and only a 500 code returned to the client, for security reasons.
+func WrapParams(f SrvFunc, contentType string) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		bytes, code := f(r.URL.Query(), r.URL.EscapedPath())
+		if len(bytes) > 0 {
+			w.Header().Set("Content-Type", contentType)
+			w.WriteHeader(code)
+			if _, err := w.Write(bytes); err != nil {
+				log.Warnf("received error writing data request %v: %v\n", r.URL.EscapedPath(), err)
+			}
+		} else {
+			w.WriteHeader(http.StatusInternalServerError)
+			if _, err := w.Write([]byte("Internal Server Error")); err != nil {
+				log.Warnf("received error writing data request %v: %v\n", r.URL.EscapedPath(), err)
+			}
+		}
+	}
+}
+
+func WrapAgeErr(errorCount threadsafe.Uint, f func() ([]byte, time.Time, error), contentType string) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		bytes, contentTime, err := f()
+		_, code := WrapErrCode(errorCount, r.URL.EscapedPath(), bytes, err)
+		w.Header().Set("Content-Type", contentType)
+		w.Header().Set("Age", fmt.Sprintf("%.0f", time.Since(contentTime).Seconds()))
+		w.WriteHeader(code)
+		log.Write(w, bytes, r.URL.EscapedPath())
+	}
+}
+
+// WrapUnpolledCheck wraps an http.HandlerFunc, returning ServiceUnavailable if any caches are unpolled; else, calling the wrapped func.
+func wrapUnpolledCheck(unpolledCaches threadsafe.UnpolledCaches, errorCount threadsafe.Uint, f http.HandlerFunc) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		if unpolledCaches.Any() {
+			HandleErr(errorCount, r.URL.EscapedPath(), fmt.Errorf("service still starting, some caches unpolled: %v", unpolledCaches.UnpolledCaches()))
+			w.WriteHeader(http.StatusServiceUnavailable)
+			log.Write(w, []byte("Service Unavailable"), r.URL.EscapedPath())
+			return
+		}
+		f(w, r)
+	}
+}
+
+const ContentTypeJSON = "application/json"
+
+// addTrailingEndpoints adds endpoints with trailing slashes to the given dispatch map. Without this, Go will match `route` and `route/` differently.
+func addTrailingSlashEndpoints(dispatchMap map[string]http.HandlerFunc) map[string]http.HandlerFunc {
+	for route, handler := range dispatchMap {
+		if strings.HasSuffix(route, "/") {
+			continue
+		}
+		dispatchMap[route+"/"] = handler
+	}
+	return dispatchMap
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/dsstat.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/dsstat.go b/traffic_monitor_golang/traffic_monitor/datareq/dsstat.go
new file mode 100644
index 0000000..e2a0bd4
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/dsstat.go
@@ -0,0 +1,20 @@
+package datareq
+
+import (
+	"encoding/json"
+	"net/http"
+	"net/url"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
+	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
+)
+
+func srvDSStats(params url.Values, errorCount threadsafe.Uint, path string, toData todata.TODataThreadsafe, dsStats threadsafe.DSStatsReader) ([]byte, int) {
+	filter, err := NewDSStatFilter(path, params, toData.Get().DeliveryServiceTypes)
+	if err != nil {
+		HandleErr(errorCount, path, err)
+		return []byte(err.Error()), http.StatusBadRequest
+	}
+	bytes, err := json.Marshal(dsStats.Get().JSON(filter, params))
+	return WrapErrCode(errorCount, path, bytes, err)
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/dsstatfilter.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/dsstatfilter.go b/traffic_monitor_golang/traffic_monitor/datareq/dsstatfilter.go
new file mode 100644
index 0000000..a29ea3a
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/dsstatfilter.go
@@ -0,0 +1,137 @@
+package datareq
+
+import (
+	"fmt"
+	"net/url"
+	"strconv"
+	"strings"
+
+	dsdata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/deliveryservicedata"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+)
+
+// DSStatFilter fulfills the cache.Filter interface, for filtering stats. See the `NewDSStatFilter` documentation for details on which query parameters are used to filter.
+type DSStatFilter struct {
+	historyCount     int
+	statsToUse       map[string]struct{}
+	wildcard         bool
+	dsType           enum.DSType
+	deliveryServices map[enum.DeliveryServiceName]struct{}
+	dsTypes          map[enum.DeliveryServiceName]enum.DSType
+}
+
+// UseDeliveryService returns whether the given delivery service is in this filter.
+func (f *DSStatFilter) UseDeliveryService(name enum.DeliveryServiceName) bool {
+	if _, inDSes := f.deliveryServices[name]; len(f.deliveryServices) != 0 && !inDSes {
+		return false
+	}
+	if f.dsType != enum.DSTypeInvalid && f.dsTypes[name] != f.dsType {
+		return false
+	}
+	return true
+}
+
+// UseStat returns whether the given stat is in this filter.
+func (f *DSStatFilter) UseStat(statName string) bool {
+	if len(f.statsToUse) == 0 {
+		return true
+	}
+	if !f.wildcard {
+		_, ok := f.statsToUse[statName]
+		return ok
+	}
+	for statToUse := range f.statsToUse {
+		if strings.Contains(statName, statToUse) {
+			return true
+		}
+	}
+	return false
+}
+
+// WithinStatHistoryMax returns whether the given history index is less than the max history of this filter.
+func (f *DSStatFilter) WithinStatHistoryMax(n int) bool {
+	if f.historyCount == 0 {
+		return true
+	}
+	if n <= f.historyCount {
+		return true
+	}
+	return false
+}
+
+// NewDSStatFilter takes the HTTP query parameters and creates a cache.Filter, filtering according to the query parameters passed.
+// Query parameters used are `hc`, `stats`, `wildcard`, `type`, and `deliveryservices`.
+// If `hc` is 0, all history is returned. If `hc` is empty, 1 history is returned.
+// If `stats` is empty, all stats are returned.
+// If `wildcard` is empty, `stats` is considered exact.
+// If `type` is empty, all types are returned.
+func NewDSStatFilter(path string, params url.Values, dsTypes map[enum.DeliveryServiceName]enum.DSType) (dsdata.Filter, error) {
+	validParams := map[string]struct{}{"hc": struct{}{}, "stats": struct{}{}, "wildcard": struct{}{}, "type": struct{}{}, "deliveryservices": struct{}{}}
+	if len(params) > len(validParams) {
+		return nil, fmt.Errorf("invalid query parameters")
+	}
+	for param := range params {
+		if _, ok := validParams[param]; !ok {
+			return nil, fmt.Errorf("invalid query parameter '%v'", param)
+		}
+	}
+
+	historyCount := 1
+	if paramHc, exists := params["hc"]; exists && len(paramHc) > 0 {
+		v, err := strconv.Atoi(paramHc[0])
+		if err == nil {
+			historyCount = v
+		}
+	}
+
+	statsToUse := map[string]struct{}{}
+	if paramStats, exists := params["stats"]; exists && len(paramStats) > 0 {
+		commaStats := strings.Split(paramStats[0], ",")
+		for _, stat := range commaStats {
+			statsToUse[stat] = struct{}{}
+		}
+	}
+
+	wildcard := false
+	if paramWildcard, exists := params["wildcard"]; exists && len(paramWildcard) > 0 {
+		wildcard, _ = strconv.ParseBool(paramWildcard[0]) // ignore errors, error => false
+	}
+
+	dsType := enum.DSTypeInvalid
+	if paramType, exists := params["type"]; exists && len(paramType) > 0 {
+		dsType = enum.DSTypeFromString(paramType[0])
+		if dsType == enum.DSTypeInvalid {
+			return nil, fmt.Errorf("invalid query parameter type '%v' - valid types are: {http, dns}", paramType[0])
+		}
+	}
+
+	deliveryServices := map[enum.DeliveryServiceName]struct{}{}
+	// TODO rename 'hosts' to 'names' for consistency
+	if paramNames, exists := params["deliveryservices"]; exists && len(paramNames) > 0 {
+		commaNames := strings.Split(paramNames[0], ",")
+		for _, name := range commaNames {
+			deliveryServices[enum.DeliveryServiceName(name)] = struct{}{}
+		}
+	}
+
+	pathArgument := getPathArgument(path)
+	if pathArgument != "" {
+		deliveryServices[enum.DeliveryServiceName(pathArgument)] = struct{}{}
+	}
+
+	// parameters without values are considered names, e.g. `?my-cache-0` or `?my-delivery-service`
+	for maybeName, val := range params {
+		if len(val) == 0 || (len(val) == 1 && val[0] == "") {
+			deliveryServices[enum.DeliveryServiceName(maybeName)] = struct{}{}
+		}
+	}
+
+	return &DSStatFilter{
+		historyCount:     historyCount,
+		statsToUse:       statsToUse,
+		wildcard:         wildcard,
+		dsType:           dsType,
+		deliveryServices: deliveryServices,
+		dsTypes:          dsTypes,
+	}, nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/eventlog.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/eventlog.go b/traffic_monitor_golang/traffic_monitor/datareq/eventlog.go
new file mode 100644
index 0000000..db50690
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/eventlog.go
@@ -0,0 +1,16 @@
+package datareq
+
+import (
+	"encoding/json"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/health"
+)
+
+// JSONEvents represents the structure we wish to serialize to JSON, for Events.
+type JSONEvents struct {
+	Events []health.Event `json:"events"`
+}
+
+func srvEventLog(events health.ThreadsafeEvents) ([]byte, error) {
+	return json.Marshal(JSONEvents{Events: events.Get()})
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/monitorconfig.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/monitorconfig.go b/traffic_monitor_golang/traffic_monitor/datareq/monitorconfig.go
new file mode 100644
index 0000000..15000ce
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/monitorconfig.go
@@ -0,0 +1,11 @@
+package datareq
+
+import (
+	"encoding/json"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
+)
+
+func srvMonitorConfig(mcThs threadsafe.TrafficMonitorConfigMap) ([]byte, error) {
+	return json.Marshal(mcThs.Get())
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/peerstate.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/peerstate.go b/traffic_monitor_golang/traffic_monitor/datareq/peerstate.go
new file mode 100644
index 0000000..b1040a8
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/peerstate.go
@@ -0,0 +1,60 @@
+package datareq
+
+import (
+	"encoding/json"
+	"net/http"
+	"net/url"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/srvhttp"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
+	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
+)
+
+// APIPeerStates contains the data to be returned for an API call to get the peer states of a Traffic Monitor. This contains common API data returned by most endpoints, and a map of peers, to caches' states.
+type APIPeerStates struct {
+	srvhttp.CommonAPIData
+	Peers map[enum.TrafficMonitorName]map[enum.CacheName][]CacheState `json:"peers"`
+}
+
+// CacheState represents the available state of a cache.
+type CacheState struct {
+	Value bool `json:"value"`
+}
+
+func srvPeerStates(params url.Values, errorCount threadsafe.Uint, path string, toData todata.TODataThreadsafe, peerStates peer.CRStatesPeersThreadsafe) ([]byte, int) {
+	filter, err := NewPeerStateFilter(path, params, toData.Get().ServerTypes)
+	if err != nil {
+		HandleErr(errorCount, path, err)
+		return []byte(err.Error()), http.StatusBadRequest
+	}
+	bytes, err := json.Marshal(createAPIPeerStates(peerStates.GetCrstates(), filter, params))
+	return WrapErrCode(errorCount, path, bytes, err)
+}
+
+func createAPIPeerStates(peerStates map[enum.TrafficMonitorName]peer.Crstates, filter *PeerStateFilter, params url.Values) APIPeerStates {
+	apiPeerStates := APIPeerStates{
+		CommonAPIData: srvhttp.GetCommonAPIData(params, time.Now()),
+		Peers:         map[enum.TrafficMonitorName]map[enum.CacheName][]CacheState{},
+	}
+
+	for peer, state := range peerStates {
+		if !filter.UsePeer(peer) {
+			continue
+		}
+		if _, ok := apiPeerStates.Peers[peer]; !ok {
+			apiPeerStates.Peers[peer] = map[enum.CacheName][]CacheState{}
+		}
+		peerState := apiPeerStates.Peers[peer]
+		for cache, available := range state.Caches {
+			if !filter.UseCache(cache) {
+				continue
+			}
+			peerState[cache] = []CacheState{CacheState{Value: available.IsAvailable}}
+		}
+		apiPeerStates.Peers[peer] = peerState
+	}
+	return apiPeerStates
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/peerstatefilter.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/peerstatefilter.go b/traffic_monitor_golang/traffic_monitor/datareq/peerstatefilter.go
new file mode 100644
index 0000000..65a114d
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/peerstatefilter.go
@@ -0,0 +1,139 @@
+package datareq
+
+import (
+	"fmt"
+	"net/url"
+	"strconv"
+	"strings"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+)
+
+// PeerStateFilter fulfills the cache.Filter interface, for filtering stats. See the `NewPeerStateFilter` documentation for details on which query parameters are used to filter.
+type PeerStateFilter struct {
+	historyCount int
+	cachesToUse  map[enum.CacheName]struct{}
+	peersToUse   map[enum.TrafficMonitorName]struct{}
+	wildcard     bool
+	cacheType    enum.CacheType
+	cacheTypes   map[enum.CacheName]enum.CacheType
+}
+
+// UsePeer returns whether the given Traffic Monitor peer is in this filter.
+func (f *PeerStateFilter) UsePeer(name enum.TrafficMonitorName) bool {
+	if _, inPeers := f.peersToUse[name]; len(f.peersToUse) != 0 && !inPeers {
+		return false
+	}
+	return true
+}
+
+// UseCache returns whether the given cache is in this filter.
+func (f *PeerStateFilter) UseCache(name enum.CacheName) bool {
+	if f.cacheType != enum.CacheTypeInvalid && f.cacheTypes[name] != f.cacheType {
+		return false
+	}
+
+	if len(f.cachesToUse) == 0 {
+		return true
+	}
+
+	if !f.wildcard {
+		_, ok := f.cachesToUse[name]
+		return ok
+	}
+	for cacheToUse := range f.cachesToUse {
+		if strings.Contains(string(name), string(cacheToUse)) {
+			return true
+		}
+	}
+	return false
+}
+
+// WithinStatHistoryMax returns whether the given history index is less than the max history of this filter.
+func (f *PeerStateFilter) WithinStatHistoryMax(n int) bool {
+	if f.historyCount == 0 {
+		return true
+	}
+	if n <= f.historyCount {
+		return true
+	}
+	return false
+}
+
+// NewPeerStateFilter takes the HTTP query parameters and creates a cache.Filter, filtering according to the query parameters passed.
+// Query parameters used are `hc`, `stats`, `wildcard`, `typep`, and `hosts`. The `stats` param filters caches. The `hosts` param filters peer Traffic Monitors. The `type` param filters cache types (edge, mid).
+// If `hc` is 0, all history is returned. If `hc` is empty, 1 history is returned.
+// If `stats` is empty, all stats are returned.
+// If `wildcard` is empty, `stats` is considered exact.
+// If `type` is empty, all cache types are returned.
+func NewPeerStateFilter(path string, params url.Values, cacheTypes map[enum.CacheName]enum.CacheType) (*PeerStateFilter, error) {
+	// TODO change legacy `stats` and `hosts` to `caches` and `monitors` (or `peers`).
+	validParams := map[string]struct{}{"hc": struct{}{}, "stats": struct{}{}, "wildcard": struct{}{}, "type": struct{}{}, "peers": struct{}{}}
+	if len(params) > len(validParams) {
+		return nil, fmt.Errorf("invalid query parameters")
+	}
+	for param := range params {
+		if _, ok := validParams[param]; !ok {
+			return nil, fmt.Errorf("invalid query parameter '%v'", param)
+		}
+	}
+
+	historyCount := 1
+	if paramHc, exists := params["hc"]; exists && len(paramHc) > 0 {
+		v, err := strconv.Atoi(paramHc[0])
+		if err == nil {
+			historyCount = v
+		}
+	}
+
+	cachesToUse := map[enum.CacheName]struct{}{}
+	// TODO rename 'stats' to 'caches'
+	if paramStats, exists := params["stats"]; exists && len(paramStats) > 0 {
+		commaStats := strings.Split(paramStats[0], ",")
+		for _, stat := range commaStats {
+			cachesToUse[enum.CacheName(stat)] = struct{}{}
+		}
+	}
+
+	wildcard := false
+	if paramWildcard, exists := params["wildcard"]; exists && len(paramWildcard) > 0 {
+		wildcard, _ = strconv.ParseBool(paramWildcard[0]) // ignore errors, error => false
+	}
+
+	cacheType := enum.CacheTypeInvalid
+	if paramType, exists := params["type"]; exists && len(paramType) > 0 {
+		cacheType = enum.CacheTypeFromString(paramType[0])
+		if cacheType == enum.CacheTypeInvalid {
+			return nil, fmt.Errorf("invalid query parameter type '%v' - valid types are: {edge, mid}", paramType[0])
+		}
+	}
+
+	peersToUse := map[enum.TrafficMonitorName]struct{}{}
+	if paramNames, exists := params["peers"]; exists && len(paramNames) > 0 {
+		commaNames := strings.Split(paramNames[0], ",")
+		for _, name := range commaNames {
+			peersToUse[enum.TrafficMonitorName(name)] = struct{}{}
+		}
+	}
+
+	pathArgument := getPathArgument(path)
+	if pathArgument != "" {
+		peersToUse[enum.TrafficMonitorName(pathArgument)] = struct{}{}
+	}
+
+	// parameters without values are considered names, e.g. `?my-cache-0` or `?my-delivery-service`
+	for maybeName, val := range params {
+		if len(val) == 0 || (len(val) == 1 && val[0] == "") {
+			peersToUse[enum.TrafficMonitorName(maybeName)] = struct{}{}
+		}
+	}
+
+	return &PeerStateFilter{
+		historyCount: historyCount,
+		cachesToUse:  cachesToUse,
+		wildcard:     wildcard,
+		cacheType:    cacheType,
+		peersToUse:   peersToUse,
+		cacheTypes:   cacheTypes,
+	}, nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/stat.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/stat.go b/traffic_monitor_golang/traffic_monitor/datareq/stat.go
new file mode 100644
index 0000000..16f93ae
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/stat.go
@@ -0,0 +1,143 @@
+package datareq
+
+import (
+	"encoding/json"
+	"math"
+	"runtime"
+	"sort"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/util"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/config"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
+)
+
+type JSONStats struct {
+	Stats Stats `json:"stats"`
+}
+
+// Stats contains statistics data about this running app. Designed to be returned via an API endpoint.
+type Stats struct {
+	MaxMemoryMB                 uint64 `json:"Max Memory (MB),string"`
+	GitRevision                 string `json:"git-revision"`
+	ErrorCount                  uint64 `json:"Error Count,string"`
+	Uptime                      uint64 `json:"uptime,string"`
+	FreeMemoryMB                uint64 `json:"Free Memory (MB),string"`
+	TotalMemoryMB               uint64 `json:"Total Memory (MB),string"`
+	Version                     string `json:"version"`
+	DeployDir                   string `json:"deploy-dir"`
+	FetchCount                  uint64 `json:"Fetch Count,string"`
+	QueryIntervalDelta          int    `json:"Query Interval Delta,string"`
+	IterationCount              uint64 `json:"Iteration Count,string"`
+	Name                        string `json:"name"`
+	BuildTimestamp              string `json:"buildTimestamp"`
+	QueryIntervalTarget         int    `json:"Query Interval Target,string"`
+	QueryIntervalActual         int    `json:"Query Interval Actual,string"`
+	SlowestCache                string `json:"Slowest Cache"`
+	LastQueryInterval           int    `json:"Last Query Interval,string"`
+	Microthreads                int    `json:"Goroutines"`
+	LastGC                      string `json:"Last Garbage Collection"`
+	MemAllocBytes               uint64 `json:"Memory Bytes Allocated"`
+	MemTotalBytes               uint64 `json:"Total Bytes Allocated"`
+	MemSysBytes                 uint64 `json:"System Bytes Allocated"`
+	OldestPolledPeer            string `json:"Oldest Polled Peer"`
+	OldestPolledPeerMs          int64  `json:"Oldest Polled Peer Time (ms)"`
+	QueryInterval95thPercentile int64  `json:"Query Interval 95th Percentile (ms)"`
+}
+
+func srvStats(staticAppData config.StaticAppData, healthPollInterval time.Duration, lastHealthDurations threadsafe.DurationMap, fetchCount threadsafe.Uint, healthIteration threadsafe.Uint, errorCount threadsafe.Uint, peerStates peer.CRStatesPeersThreadsafe) ([]byte, error) {
+	return getStats(staticAppData, healthPollInterval, lastHealthDurations.Get(), fetchCount.Get(), healthIteration.Get(), errorCount.Get(), peerStates)
+}
+
+func getStats(staticAppData config.StaticAppData, pollingInterval time.Duration, lastHealthTimes map[enum.CacheName]time.Duration, fetchCount uint64, healthIteration uint64, errorCount uint64, peerStates peer.CRStatesPeersThreadsafe) ([]byte, error) {
+	longestPollCache, longestPollTime := getLongestPoll(lastHealthTimes)
+	var memStats runtime.MemStats
+	runtime.ReadMemStats(&memStats)
+
+	var s Stats
+	s.MaxMemoryMB = memStats.TotalAlloc / (1024 * 1024)
+	s.GitRevision = staticAppData.GitRevision
+	s.ErrorCount = errorCount
+	s.Uptime = uint64(time.Since(staticAppData.StartTime) / time.Second)
+	s.FreeMemoryMB = staticAppData.FreeMemoryMB
+	s.TotalMemoryMB = memStats.Alloc / (1024 * 1024) // TODO rename to "used memory" if/when nothing is using the JSON entry
+	s.Version = staticAppData.Version
+	s.DeployDir = staticAppData.WorkingDir
+	s.FetchCount = fetchCount
+	s.SlowestCache = string(longestPollCache)
+	s.IterationCount = healthIteration
+	s.Name = staticAppData.Name
+	s.BuildTimestamp = staticAppData.BuildTimestamp
+	s.QueryIntervalTarget = int(pollingInterval / time.Millisecond)
+	s.QueryIntervalActual = int(longestPollTime / time.Millisecond)
+	s.QueryIntervalDelta = s.QueryIntervalActual - s.QueryIntervalTarget
+	s.LastQueryInterval = int(math.Max(float64(s.QueryIntervalActual), float64(s.QueryIntervalTarget)))
+	s.Microthreads = runtime.NumGoroutine()
+	s.LastGC = time.Unix(0, int64(memStats.LastGC)).String()
+	s.MemAllocBytes = memStats.Alloc
+	s.MemTotalBytes = memStats.TotalAlloc
+	s.MemSysBytes = memStats.Sys
+
+	oldestPolledPeer, oldestPolledPeerTime := oldestPeerPollTime(peerStates.GetQueryTimes()) // map[enum.TrafficMonitorName]time.Time)
+	s.OldestPolledPeer = string(oldestPolledPeer)
+	s.OldestPolledPeerMs = time.Now().Sub((oldestPolledPeerTime)).Nanoseconds() / util.MillisecondsPerNanosecond
+
+	s.QueryInterval95thPercentile = getCacheTimePercentile(lastHealthTimes, 0.95).Nanoseconds() / util.MillisecondsPerNanosecond
+
+	return json.Marshal(JSONStats{Stats: s})
+}
+
+func getLongestPoll(lastHealthTimes map[enum.CacheName]time.Duration) (enum.CacheName, time.Duration) {
+	var longestCache enum.CacheName
+	var longestTime time.Duration
+	for cache, time := range lastHealthTimes {
+		if time > longestTime {
+			longestTime = time
+			longestCache = cache
+		}
+	}
+	return longestCache, longestTime
+}
+
+type Durations []time.Duration
+
+func (s Durations) Len() int {
+	return len(s)
+}
+func (s Durations) Less(i, j int) bool {
+	return s[i] < s[j]
+}
+func (s Durations) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}
+
+// getCacheTimePercentile returns the given percentile of cache result times. The `percentile` should be a decimal percent, for example, for the 95th percentile pass 0.95
+func getCacheTimePercentile(lastHealthTimes map[enum.CacheName]time.Duration, percentile float64) time.Duration {
+	times := make([]time.Duration, 0, len(lastHealthTimes))
+	for _, t := range lastHealthTimes {
+		times = append(times, t)
+	}
+	sort.Sort(Durations(times))
+
+	n := int(float64(len(lastHealthTimes)) * percentile)
+
+	return times[n]
+}
+
+func oldestPeerPollTime(peerTimes map[enum.TrafficMonitorName]time.Time) (enum.TrafficMonitorName, time.Time) {
+	now := time.Now()
+	oldestTime := now
+	oldestPeer := enum.TrafficMonitorName("")
+	for p, t := range peerTimes {
+		if oldestTime.After(t) {
+			oldestTime = t
+			oldestPeer = p
+		}
+	}
+	if oldestTime == now {
+		oldestTime = time.Time{}
+	}
+	return oldestPeer, oldestTime
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/statsummary.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/statsummary.go b/traffic_monitor_golang/traffic_monitor/datareq/statsummary.go
new file mode 100644
index 0000000..85f2cbd
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/statsummary.go
@@ -0,0 +1,98 @@
+package datareq
+
+import (
+	"encoding/json"
+	"net/http"
+	"net/url"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/util"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/cache"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/srvhttp"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
+	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
+)
+
+type StatSummary struct {
+	Caches map[enum.CacheName]map[string]StatSummaryStat `json:"caches"`
+	srvhttp.CommonAPIData
+}
+
+type StatSummaryStat struct {
+	DataPointCount int64   `json:"dpCount"`
+	Start          float64 `json:"start"`
+	End            float64 `json:"end"`
+	High           float64 `json:"high"`
+	Low            float64 `json:"low"`
+	Average        float64 `json:"average"`
+	StartTime      int64   `json:"startTime"`
+	EndTime        int64   `json:"endTime"`
+}
+
+func srvStatSummary(params url.Values, errorCount threadsafe.Uint, path string, toData todata.TODataThreadsafe, statResultHistory threadsafe.ResultStatHistory) ([]byte, int) {
+	filter, err := NewCacheStatFilter(path, params, toData.Get().ServerTypes)
+	if err != nil {
+		HandleErr(errorCount, path, err)
+		return []byte(err.Error()), http.StatusBadRequest
+	}
+	bytes, err := json.Marshal(createStatSummary(statResultHistory.Get(), filter, params))
+	return WrapErrCode(errorCount, path, bytes, err)
+}
+
+func createStatSummary(statResultHistory cache.ResultStatHistory, filter cache.Filter, params url.Values) StatSummary {
+	statPrefix := "ats."
+	ss := StatSummary{
+		Caches:        map[enum.CacheName]map[string]StatSummaryStat{},
+		CommonAPIData: srvhttp.GetCommonAPIData(params, time.Now()),
+	}
+	for cache, stats := range statResultHistory {
+		if !filter.UseCache(cache) {
+			continue
+		}
+		ssStats := map[string]StatSummaryStat{}
+		for statName, statHistory := range stats {
+			if !filter.UseStat(statName) {
+				continue
+			}
+			if len(statHistory) == 0 {
+				continue
+			}
+			ssStat := StatSummaryStat{}
+			msPerNs := int64(1000000)
+			ssStat.StartTime = statHistory[len(statHistory)-1].Time.UnixNano() / msPerNs
+			ssStat.EndTime = statHistory[0].Time.UnixNano() / msPerNs
+			oldestVal, isOldestValNumeric := util.ToNumeric(statHistory[len(statHistory)-1].Val)
+			newestVal, isNewestValNumeric := util.ToNumeric(statHistory[0].Val)
+			if !isOldestValNumeric || !isNewestValNumeric {
+				continue // skip non-numeric stats
+			}
+			ssStat.Start = oldestVal
+			ssStat.End = newestVal
+			ssStat.High = newestVal
+			ssStat.Low = newestVal
+			for _, val := range statHistory {
+				fVal, ok := util.ToNumeric(val.Val)
+				if !ok {
+					log.Warnf("threshold stat %v value %v is not a number, cannot use.", statName, val.Val)
+					continue
+				}
+				for i := uint64(0); i < val.Span; i++ {
+					ssStat.DataPointCount++
+					ssStat.Average -= ssStat.Average / float64(ssStat.DataPointCount)
+					ssStat.Average += fVal / float64(ssStat.DataPointCount)
+				}
+				if fVal < ssStat.Low {
+					ssStat.Low = fVal
+				}
+				if fVal > ssStat.High {
+					ssStat.High = fVal
+				}
+			}
+			ssStats[statPrefix+statName] = ssStat
+		}
+		ss.Caches[cache] = ssStats
+	}
+	return ss
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/trafficopsuri.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/trafficopsuri.go b/traffic_monitor_golang/traffic_monitor/datareq/trafficopsuri.go
new file mode 100644
index 0000000..6c2abee
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/trafficopsuri.go
@@ -0,0 +1,9 @@
+package datareq
+
+import (
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/threadsafe"
+)
+
+func srvAPITrafficOpsURI(opsConfig threadsafe.OpsConfig) []byte {
+	return []byte(opsConfig.Get().Url)
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aca1457e/traffic_monitor_golang/traffic_monitor/datareq/version.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/datareq/version.go b/traffic_monitor_golang/traffic_monitor/datareq/version.go
new file mode 100644
index 0000000..2e7840b
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/datareq/version.go
@@ -0,0 +1,15 @@
+package datareq
+
+import (
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/config"
+)
+
+func srvAPIVersion(staticAppData config.StaticAppData) []byte {
+	s := "traffic_monitor-" + staticAppData.Version + "."
+	if len(staticAppData.GitRevision) > 6 {
+		s += staticAppData.GitRevision[:6]
+	} else {
+		s += staticAppData.GitRevision
+	}
+	return []byte(s)
+}


[3/3] incubator-trafficcontrol git commit: This closes #323

Posted by ne...@apache.org.
This closes #323


Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/d5692bb4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/d5692bb4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/d5692bb4

Branch: refs/heads/master
Commit: d5692bb4691f95a9a92add4c50c854e13c253951
Parents: aca1457
Author: David Neuman <da...@gmail.com>
Authored: Wed Mar 1 20:52:47 2017 -0700
Committer: David Neuman <da...@gmail.com>
Committed: Wed Mar 1 20:52:47 2017 -0700

----------------------------------------------------------------------

----------------------------------------------------------------------