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/01/30 15:29:07 UTC

[01/19] incubator-trafficcontrol git commit: Add TM2 event.log location to conf, logrotate

Repository: incubator-trafficcontrol
Updated Branches:
  refs/heads/master 43cfb5a19 -> 499bcbc29


Add TM2 event.log location to conf, logrotate


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

Branch: refs/heads/master
Commit: 129a47e2b61c874f305c33d757cdddb6bf8fc6ed
Parents: b36c65e
Author: Robert Butts <ro...@gmail.com>
Authored: Thu Jan 26 15:11:37 2017 -0700
Committer: Dave Neuman <ne...@apache.org>
Committed: Mon Jan 30 08:28:46 2017 -0700

----------------------------------------------------------------------
 traffic_monitor_golang/build/traffic_monitor.logrotate | 10 ++++++++++
 traffic_monitor_golang/conf/traffic_monitor.cfg        |  6 +++---
 2 files changed, 13 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/129a47e2/traffic_monitor_golang/build/traffic_monitor.logrotate
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/build/traffic_monitor.logrotate b/traffic_monitor_golang/build/traffic_monitor.logrotate
index e7073ad..b52c013 100644
--- a/traffic_monitor_golang/build/traffic_monitor.logrotate
+++ b/traffic_monitor_golang/build/traffic_monitor.logrotate
@@ -24,3 +24,13 @@
         rotate 5
         copytruncate
 }
+
+/opt/traffic_monitor/var/log/event.log {
+        compress
+        maxage 30
+        missingok
+        nomail
+        size 10M
+        rotate 5
+        copytruncate
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/129a47e2/traffic_monitor_golang/conf/traffic_monitor.cfg
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/conf/traffic_monitor.cfg b/traffic_monitor_golang/conf/traffic_monitor.cfg
index f6698d1..f99447f 100644
--- a/traffic_monitor_golang/conf/traffic_monitor.cfg
+++ b/traffic_monitor_golang/conf/traffic_monitor.cfg
@@ -10,9 +10,9 @@
 	"max_health_history": 5,
 	"health_flush_interval_ms": 20,
 	"stat_flush_interval_ms": 20,
-	"log_location_event": "null",
-	"log_location_error": "stderr",
-	"log_location_warning": "stdout",
+	"log_location_event": "/opt/traffic_monitor/var/log/event.log",
+	"log_location_error": "/opt/traffic_monitor/var/log/traffic_monitor.log",
+	"log_location_warning": "/opt/traffic_monitor/var/log/traffic_monitor.log",
 	"log_location_info": "null",
 	"log_location_debug": "null",
 	"serve_read_timeout_ms": 10000,


[14/19] incubator-trafficcontrol git commit: Move TM2 to trafficcontrol/traffic_monitor_golang

Posted by ne...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/manager/datarequest.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/manager/datarequest.go b/traffic_monitor/experimental/traffic_monitor/manager/datarequest.go
deleted file mode 100644
index 4f1f83a..0000000
--- a/traffic_monitor/experimental/traffic_monitor/manager/datarequest.go
+++ /dev/null
@@ -1,1221 +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"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/util"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/cache"
-	ds "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/deliveryservice"
-	dsdata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/deliveryservicedata"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/health"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/peer"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/srvhttp"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/threadsafe"
-	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/trafficopsdata"
-	towrap "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/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 srvTRConfig(opsConfig OpsConfigThreadsafe, toSession towrap.ITrafficOpsSession) ([]byte, error) {
-	cdnName := opsConfig.Get().CdnName
-	if toSession == nil {
-		return nil, fmt.Errorf("Unable to connect to Traffic Ops")
-	}
-	if cdnName == "" {
-		return nil, fmt.Errorf("No CDN Configured")
-	}
-	return toSession.CRConfigRaw(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) ([]byte, error) {
-	return getStats(staticAppData, healthPollInterval, lastHealthDurations.Get(), fetchCount.Get(), healthIteration.Get(), errorCount.Get())
-}
-
-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(WrapErr(errorCount, func() ([]byte, 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)
-		}, 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)),
-	}
-	return addTrailingSlashEndpoints(dispatchMap)
-}
-
-// 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
-}
-
-// 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"`
-}
-
-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 getStats(staticAppData StaticAppData, pollingInterval time.Duration, lastHealthTimes map[enum.CacheName]time.Duration, fetchCount uint64, healthIteration uint64, errorCount uint64) ([]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
-
-	return json.Marshal(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{}
-			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
-}
-
-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/594b8517/traffic_monitor/experimental/traffic_monitor/manager/health.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/manager/health.go b/traffic_monitor/experimental/traffic_monitor/manager/health.go
deleted file mode 100644
index a4465cf..0000000
--- a/traffic_monitor/experimental/traffic_monitor/manager/health.go
+++ /dev/null
@@ -1,245 +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 (
-	"sync"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/cache"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/config"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/health"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/peer"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/threadsafe"
-	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/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
-// doesn't include all stat data needed for e.g. delivery service calculations.4
-// Returns the last health durations, events, the local cache statuses, and the health result history.
-func StartHealthResultManager(
-	cacheHealthChan <-chan cache.Result,
-	toData todata.TODataThreadsafe,
-	localStates peer.CRStatesThreadsafe,
-	monitorConfig TrafficMonitorConfigMapThreadsafe,
-	peerStates peer.CRStatesPeersThreadsafe,
-	combinedStates peer.CRStatesThreadsafe,
-	fetchCount threadsafe.Uint,
-	errorCount threadsafe.Uint,
-	cfg config.Config,
-	events health.ThreadsafeEvents,
-	localCacheStatus threadsafe.CacheAvailableStatus,
-) (DurationMapThreadsafe, threadsafe.ResultHistory) {
-	lastHealthDurations := NewDurationMapThreadsafe()
-	healthHistory := threadsafe.NewResultHistory()
-	go healthResultManagerListen(
-		cacheHealthChan,
-		toData,
-		localStates,
-		lastHealthDurations,
-		healthHistory,
-		monitorConfig,
-		peerStates,
-		combinedStates,
-		fetchCount,
-		errorCount,
-		events,
-		localCacheStatus,
-		cfg,
-	)
-	return lastHealthDurations, healthHistory
-}
-
-func healthResultManagerListen(
-	cacheHealthChan <-chan cache.Result,
-	toData todata.TODataThreadsafe,
-	localStates peer.CRStatesThreadsafe,
-	lastHealthDurations DurationMapThreadsafe,
-	healthHistory threadsafe.ResultHistory,
-	monitorConfig TrafficMonitorConfigMapThreadsafe,
-	peerStates peer.CRStatesPeersThreadsafe,
-	combinedStates peer.CRStatesThreadsafe,
-	fetchCount threadsafe.Uint,
-	errorCount threadsafe.Uint,
-	events health.ThreadsafeEvents,
-	localCacheStatus threadsafe.CacheAvailableStatus,
-	cfg config.Config,
-) {
-	lastHealthEndTimes := map[enum.CacheName]time.Time{}
-	// This reads at least 1 value from the cacheHealthChan. Then, we loop, and try to read from the channel some more. If there's nothing to read, we hit `default` and process. If there is stuff to read, we read it, then inner-loop trying to read more. If we're continuously reading and the channel is never empty, and we hit the tick time, process anyway even though the channel isn't empty, to prevent never processing (starvation).
-	var ticker *time.Ticker
-
-	process := func(results []cache.Result) {
-		processHealthResult(
-			cacheHealthChan,
-			toData,
-			localStates,
-			lastHealthDurations,
-			monitorConfig,
-			peerStates,
-			combinedStates,
-			fetchCount,
-			errorCount,
-			events,
-			localCacheStatus,
-			lastHealthEndTimes,
-			healthHistory,
-			results,
-			cfg,
-		)
-	}
-
-	for {
-		var results []cache.Result
-		results = append(results, <-cacheHealthChan)
-		if ticker != nil {
-			ticker.Stop()
-		}
-		ticker = time.NewTicker(cfg.HealthFlushInterval)
-	innerLoop:
-		for {
-			select {
-			case <-ticker.C:
-				log.Infof("Health Result Manager flushing queued results\n")
-				process(results)
-				break innerLoop
-			default:
-				select {
-				case r := <-cacheHealthChan:
-					results = append(results, r)
-				default:
-					process(results)
-					break innerLoop
-				}
-			}
-		}
-	}
-}
-
-// processHealthResult processes the given health results, adding their stats to the CacheAvailableStatus. Note this is NOT threadsafe, because it non-atomically gets CacheAvailableStatuses, Events, LastHealthDurations and later updates them. This MUST NOT be called from multiple threads.
-func processHealthResult(
-	cacheHealthChan <-chan cache.Result,
-	toData todata.TODataThreadsafe,
-	localStates peer.CRStatesThreadsafe,
-	lastHealthDurationsThreadsafe DurationMapThreadsafe,
-	monitorConfig TrafficMonitorConfigMapThreadsafe,
-	peerStates peer.CRStatesPeersThreadsafe,
-	combinedStates peer.CRStatesThreadsafe,
-	fetchCount threadsafe.Uint,
-	errorCount threadsafe.Uint,
-	events health.ThreadsafeEvents,
-	localCacheStatusThreadsafe threadsafe.CacheAvailableStatus,
-	lastHealthEndTimes map[enum.CacheName]time.Time,
-	healthHistory threadsafe.ResultHistory,
-	results []cache.Result,
-	cfg config.Config,
-) {
-	if len(results) == 0 {
-		return
-	}
-	defer func() {
-		for _, r := range results {
-			log.Debugf("poll %v %v finish\n", r.PollID, time.Now())
-			r.PollFinished <- r.PollID
-		}
-	}()
-
-	toDataCopy := toData.Get() // create a copy, so the same data used for all processing of this cache health result
-	monitorConfigCopy := monitorConfig.Get()
-	healthHistoryCopy := healthHistory.Get().Copy()
-	for i, healthResult := range results {
-		fetchCount.Inc()
-		var prevResult cache.Result
-		healthResultHistory := healthHistoryCopy[healthResult.ID]
-		if len(healthResultHistory) != 0 {
-			prevResult = healthResultHistory[len(healthResultHistory)-1]
-		}
-
-		if healthResult.Error == nil {
-			health.GetVitals(&healthResult, &prevResult, &monitorConfigCopy)
-			results[i] = healthResult
-		}
-
-		maxHistory := uint64(monitorConfigCopy.Profile[monitorConfigCopy.TrafficServer[string(healthResult.ID)].Profile].Parameters.HistoryCount)
-		if maxHistory < 1 {
-			log.Infof("processHealthResult got history count %v for %v, setting to 1\n", maxHistory, healthResult.ID)
-			maxHistory = 1
-		}
-
-		healthHistoryCopy[healthResult.ID] = pruneHistory(append([]cache.Result{healthResult}, healthHistoryCopy[healthResult.ID]...), maxHistory)
-	}
-
-	health.CalcAvailability(results, "health", nil, monitorConfigCopy, toDataCopy, localCacheStatusThreadsafe, localStates, events)
-
-	healthHistory.Set(healthHistoryCopy)
-	// TODO determine if we should combineCrStates() here
-
-	lastHealthDurations := lastHealthDurationsThreadsafe.Get().Copy()
-	for _, healthResult := range results {
-		if lastHealthStart, ok := lastHealthEndTimes[healthResult.ID]; ok {
-			d := time.Since(lastHealthStart)
-			lastHealthDurations[healthResult.ID] = d
-		}
-		lastHealthEndTimes[healthResult.ID] = time.Now()
-	}
-	lastHealthDurationsThreadsafe.Set(lastHealthDurations)
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/manager/manager.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/manager/manager.go b/traffic_monitor/experimental/traffic_monitor/manager/manager.go
deleted file mode 100644
index c869365..0000000
--- a/traffic_monitor/experimental/traffic_monitor/manager/manager.go
+++ /dev/null
@@ -1,176 +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 (
-	"crypto/tls"
-	"net/http"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/fetcher"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/handler"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/poller"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/cache"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/config"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/health"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/peer"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/threadsafe"
-	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/trafficopsdata"
-	towrap "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/trafficopswrapper"
-	"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
-}
-
-//
-// Start starts the poller and handler goroutines
-//
-func Start(opsConfigFile string, cfg config.Config, staticAppData StaticAppData) {
-	toSession := towrap.ITrafficOpsSession(towrap.NewTrafficOpsSessionThreadsafe(nil))
-	counters := fetcher.Counters{
-		Success: gmx.NewCounter("fetchSuccess"),
-		Fail:    gmx.NewCounter("fetchFail"),
-		Pending: gmx.NewGauge("fetchPending"),
-	}
-
-	sharedClient := &http.Client{
-		Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
-		Timeout:   cfg.HTTPTimeout,
-	}
-
-	localStates := peer.NewCRStatesThreadsafe()     // this is the local state as discoverer by this traffic_monitor
-	peerStates := peer.NewCRStatesPeersThreadsafe() // each peer's last state is saved in this map
-	fetchCount := threadsafe.NewUint()              // note this is the number of individual caches fetched from, not the number of times all the caches were polled.
-	healthIteration := threadsafe.NewUint()
-	errorCount := threadsafe.NewUint()
-
-	toData := todata.NewThreadsafe()
-
-	cacheHealthHandler := cache.NewHandler()
-	cacheHealthPoller := poller.NewHTTP(cfg.CacheHealthPollingInterval, true, sharedClient, counters, cacheHealthHandler, cfg.HTTPPollNoSleep)
-	cacheStatHandler := cache.NewPrecomputeHandler(toData, peerStates)
-	cacheStatPoller := poller.NewHTTP(cfg.CacheStatPollingInterval, false, sharedClient, counters, cacheStatHandler, cfg.HTTPPollNoSleep)
-	monitorConfigPoller := poller.NewMonitorConfig(cfg.MonitorConfigPollingInterval)
-	peerHandler := peer.NewHandler()
-	peerPoller := poller.NewHTTP(cfg.PeerPollingInterval, false, sharedClient, counters, peerHandler, cfg.HTTPPollNoSleep)
-
-	go monitorConfigPoller.Poll()
-	go cacheHealthPoller.Poll()
-	go cacheStatPoller.Poll()
-	go peerPoller.Poll()
-
-	events := health.NewThreadsafeEvents(cfg.MaxEvents)
-
-	cachesChanged := make(chan struct{})
-
-	monitorConfig := StartMonitorConfigManager(
-		monitorConfigPoller.ConfigChannel,
-		localStates,
-		cacheStatPoller.ConfigChannel,
-		cacheHealthPoller.ConfigChannel,
-		peerPoller.ConfigChannel,
-		cachesChanged,
-		cfg,
-		staticAppData,
-	)
-
-	combinedStates, events := StartPeerManager(
-		peerHandler.ResultChannel,
-		localStates,
-		peerStates,
-		events,
-		cfg.PeerOptimistic, // TODO remove
-		toData,
-		cfg,
-	)
-
-	statInfoHistory, statResultHistory, statMaxKbpses, _, lastKbpsStats, dsStats, unpolledCaches, localCacheStatus := StartStatHistoryManager(
-		cacheStatHandler.ResultChan(),
-		localStates,
-		combinedStates,
-		toData,
-		cachesChanged,
-		errorCount,
-		cfg,
-		monitorConfig,
-		events,
-	)
-
-	lastHealthDurations, healthHistory := StartHealthResultManager(
-		cacheHealthHandler.ResultChan(),
-		toData,
-		localStates,
-		monitorConfig,
-		peerStates,
-		combinedStates,
-		fetchCount,
-		errorCount,
-		cfg,
-		events,
-		localCacheStatus,
-	)
-
-	StartOpsConfigManager(
-		opsConfigFile,
-		toSession,
-		toData,
-		[]chan<- handler.OpsConfig{monitorConfigPoller.OpsConfigChannel},
-		[]chan<- towrap.ITrafficOpsSession{monitorConfigPoller.SessionChannel},
-		localStates,
-		peerStates,
-		combinedStates,
-		statInfoHistory,
-		statResultHistory,
-		statMaxKbpses,
-		healthHistory,
-		lastKbpsStats,
-		dsStats,
-		events,
-		staticAppData,
-		cacheHealthPoller.Config.Interval,
-		lastHealthDurations,
-		fetchCount,
-		healthIteration,
-		errorCount,
-		localCacheStatus,
-		unpolledCaches,
-		monitorConfig,
-		cfg,
-	)
-
-	healthTickListener(cacheHealthPoller.TickChan, healthIteration)
-}
-
-// healthTickListener listens for health ticks, and writes to the health iteration variable. Does not return.
-func healthTickListener(cacheHealthTick <-chan uint64, healthIteration threadsafe.Uint) {
-	for i := range cacheHealthTick {
-		healthIteration.Set(i)
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/manager/monitorconfig.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/manager/monitorconfig.go b/traffic_monitor/experimental/traffic_monitor/manager/monitorconfig.go
deleted file mode 100644
index 76350d4..0000000
--- a/traffic_monitor/experimental/traffic_monitor/manager/monitorconfig.go
+++ /dev/null
@@ -1,282 +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 (
-	"fmt"
-	"strings"
-	"sync"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/poller"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/config"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/peer"
-	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,
-	localStates peer.CRStatesThreadsafe,
-	statURLSubscriber chan<- poller.HttpPollerConfig,
-	healthURLSubscriber chan<- poller.HttpPollerConfig,
-	peerURLSubscriber chan<- poller.HttpPollerConfig,
-	cachesChangeSubscriber chan<- struct{},
-	cfg config.Config,
-	staticAppData StaticAppData,
-) TrafficMonitorConfigMapThreadsafe {
-	monitorConfig := NewTrafficMonitorConfigMapThreadsafe()
-	go monitorConfigListen(monitorConfig,
-		monitorConfigPollChan,
-		localStates,
-		statURLSubscriber,
-		healthURLSubscriber,
-		peerURLSubscriber,
-		cachesChangeSubscriber,
-		cfg,
-		staticAppData,
-	)
-	return monitorConfig
-}
-
-// trafficOpsHealthConnectionTimeoutToDuration takes the int from Traffic Ops, which is in milliseconds, and returns a time.Duration
-// TODO change Traffic Ops Client API to a time.Duration
-func trafficOpsHealthConnectionTimeoutToDuration(t int) time.Duration {
-	return time.Duration(t) * time.Millisecond
-}
-
-// trafficOpsPeerPollIntervalToDuration takes the int from Traffic Ops, which is in milliseconds, and returns a time.Duration
-// TODO change Traffic Ops Client API to a time.Duration
-func trafficOpsPeerPollIntervalToDuration(t int) time.Duration {
-	return time.Duration(t) * time.Millisecond
-}
-
-// trafficOpsStatPollIntervalToDuration takes the int from Traffic Ops, which is in milliseconds, and returns a time.Duration
-// TODO change Traffic Ops Client API to a time.Duration
-func trafficOpsStatPollIntervalToDuration(t int) time.Duration {
-	return time.Duration(t) * time.Millisecond
-}
-
-// trafficOpsHealthPollIntervalToDuration takes the int from Traffic Ops, which is in milliseconds, and returns a time.Duration
-// TODO change Traffic Ops Client API to a time.Duration
-func trafficOpsHealthPollIntervalToDuration(t int) time.Duration {
-	return time.Duration(t) * time.Millisecond
-}
-
-var healthPollCount int
-
-// getPollIntervals reads the Traffic Ops Client monitorConfig structure, and parses and returns the health, peer, and stat poll intervals
-func getHealthPeerStatPollIntervals(monitorConfig to.TrafficMonitorConfigMap, cfg config.Config) (time.Duration, time.Duration, time.Duration, error) {
-	peerPollIntervalI, peerPollIntervalExists := monitorConfig.Config["peers.polling.interval"]
-	if !peerPollIntervalExists {
-		return 0, 0, 0, fmt.Errorf("Traffic Ops Monitor config missing 'peers.polling.interval', not setting config changes.\n")
-	}
-	peerPollIntervalInt, peerPollIntervalIsInt := peerPollIntervalI.(float64)
-	if !peerPollIntervalIsInt {
-		return 0, 0, 0, fmt.Errorf("Traffic Ops Monitor config 'peers.polling.interval' value '%v' type %T is not an integer, not setting config changes.\n", peerPollIntervalI, peerPollIntervalI)
-	}
-	peerPollInterval := trafficOpsPeerPollIntervalToDuration(int(peerPollIntervalInt))
-
-	statPollIntervalI, statPollIntervalExists := monitorConfig.Config["health.polling.interval"]
-	if !statPollIntervalExists {
-		return 0, 0, 0, fmt.Errorf("Traffic Ops Monitor config missing 'health.polling.interval', not setting config changes.\n")
-	}
-	statPollIntervalInt, statPollIntervalIsInt := statPollIntervalI.(float64)
-	if !statPollIntervalIsInt {
-		return 0, 0, 0, fmt.Errorf("Traffic Ops Monitor config 'health.polling.interval' value '%v' type %T is not an integer, not setting config changes.\n", statPollIntervalI, statPollIntervalI)
-	}
-	statPollInterval := trafficOpsStatPollIntervalToDuration(int(statPollIntervalInt))
-
-	healthPollIntervalI, healthPollIntervalExists := monitorConfig.Config["heartbeat.polling.interval"]
-	healthPollIntervalInt, healthPollIntervalIsInt := healthPollIntervalI.(float64)
-	if !healthPollIntervalExists {
-		if healthPollCount == 0 { //only log this once
-			log.Warnln("Traffic Ops Monitor config missing 'heartbeat.polling.interval', using health for heartbeat.")
-			healthPollCount++
-		}
-		healthPollIntervalInt = statPollIntervalInt
-	} else if !healthPollIntervalIsInt {
-		log.Warnf("Traffic Ops Monitor config 'heartbeat.polling.interval' value '%v' type %T is not an integer, using health for heartbeat\n", statPollIntervalI, statPollIntervalI)
-		healthPollIntervalInt = statPollIntervalInt
-	}
-	healthPollInterval := trafficOpsHealthPollIntervalToDuration(int(healthPollIntervalInt))
-
-	return healthPollInterval, peerPollInterval, statPollInterval, nil
-}
-
-// 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,
-	monitorConfigPollChan <-chan to.TrafficMonitorConfigMap,
-	localStates peer.CRStatesThreadsafe,
-	statURLSubscriber chan<- poller.HttpPollerConfig,
-	healthURLSubscriber chan<- poller.HttpPollerConfig,
-	peerURLSubscriber chan<- poller.HttpPollerConfig,
-	cachesChangeSubscriber chan<- struct{},
-	cfg config.Config,
-	staticAppData StaticAppData,
-) {
-	for monitorConfig := range monitorConfigPollChan {
-		monitorConfigTS.Set(monitorConfig)
-		healthURLs := map[string]poller.PollConfig{}
-		statURLs := map[string]poller.PollConfig{}
-		peerURLs := map[string]poller.PollConfig{}
-		caches := map[string]string{}
-
-		healthPollInterval, peerPollInterval, statPollInterval, err := getHealthPeerStatPollIntervals(monitorConfig, cfg)
-		if err != nil {
-			continue
-		}
-
-		for _, srv := range monitorConfig.TrafficServer {
-			caches[srv.HostName] = srv.Status
-
-			cacheName := enum.CacheName(srv.HostName)
-
-			srvStatus := enum.CacheStatusFromString(srv.Status)
-			if srvStatus == enum.CacheStatusOnline {
-				localStates.SetCache(cacheName, peer.IsAvailable{IsAvailable: true})
-				continue
-			}
-			if srvStatus == enum.CacheStatusOffline {
-				continue
-			}
-			// seed states with available = false until our polling cycle picks up a result
-			if _, exists := localStates.GetCache(cacheName); !exists {
-				localStates.SetCache(cacheName, peer.IsAvailable{IsAvailable: false})
-			}
-
-			url := monitorConfig.Profile[srv.Profile].Parameters.HealthPollingURL
-			if url == "" {
-				log.Errorf("monitor config server %v profile %v has no polling URL; can't poll", srv.HostName, srv.Profile)
-				continue
-			}
-			r := strings.NewReplacer(
-				"${hostname}", srv.IP,
-				"${interface_name}", srv.InterfaceName,
-				"application=plugin.remap", "application=system",
-				"application=", "application=system",
-			)
-			url = r.Replace(url)
-
-			connTimeout := trafficOpsHealthConnectionTimeoutToDuration(monitorConfig.Profile[srv.Profile].Parameters.HealthConnectionTimeout)
-			healthURLs[srv.HostName] = poller.PollConfig{URL: url, Timeout: connTimeout}
-			r = strings.NewReplacer("application=system", "application=")
-			statURL := r.Replace(url)
-			statURLs[srv.HostName] = poller.PollConfig{URL: statURL, Timeout: connTimeout}
-		}
-
-		for _, srv := range monitorConfig.TrafficMonitor {
-			if srv.HostName == staticAppData.Hostname {
-				continue
-			}
-			if enum.CacheStatusFromString(srv.Status) != enum.CacheStatusOnline {
-				continue
-			}
-			// TODO: the URL should be config driven. -jse
-			url := fmt.Sprintf("http://%s:%d/publish/CrStates?raw", srv.IP, srv.Port)
-			peerURLs[srv.HostName] = poller.PollConfig{URL: url} // TODO determine timeout.
-		}
-
-		statURLSubscriber <- poller.HttpPollerConfig{Urls: statURLs, Interval: statPollInterval}
-		healthURLSubscriber <- poller.HttpPollerConfig{Urls: healthURLs, Interval: healthPollInterval}
-		peerURLSubscriber <- poller.HttpPollerConfig{Urls: peerURLs, Interval: peerPollInterval}
-
-		for cacheName := range localStates.GetCaches() {
-			if _, exists := monitorConfig.TrafficServer[string(cacheName)]; !exists {
-				log.Warnf("Removing %s from localStates", cacheName)
-				localStates.DeleteCache(cacheName)
-			}
-		}
-
-		cachesChangeSubscriber <- struct{}{}
-
-		// TODO because there are multiple writers to localStates.DeliveryService, there is a race condition, where MonitorConfig (this func) and HealthResultManager could write at the same time, and the HealthResultManager could overwrite a delivery service addition or deletion here. Probably the simplest and most performant fix would be a lock-free algorithm using atomic compare-and-swaps.
-		for _, ds := range monitorConfig.DeliveryService {
-			// since caches default to unavailable, also default DS false
-			if _, exists := localStates.GetDeliveryService(enum.DeliveryServiceName(ds.XMLID)); !exists {
-				localStates.SetDeliveryService(enum.DeliveryServiceName(ds.XMLID), peer.Deliveryservice{IsAvailable: false, DisabledLocations: []enum.CacheName{}}) // important to initialize DisabledLocations, so JSON is `[]` not `null`
-			}
-		}
-		for ds := range localStates.GetDeliveryServices() {
-			if _, exists := monitorConfig.DeliveryService[string(ds)]; !exists {
-				localStates.DeleteDeliveryService(ds)
-			}
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/manager/opsconfig.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/manager/opsconfig.go b/traffic_monitor/experimental/traffic_monitor/manager/opsconfig.go
deleted file mode 100644
index 8f7a0d2..0000000
--- a/traffic_monitor/experimental/traffic_monitor/manager/opsconfig.go
+++ /dev/null
@@ -1,186 +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 (
-	"fmt"
-	"sync"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/handler"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/poller"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/config"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/health"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/peer"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/srvhttp"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/threadsafe"
-	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/trafficopsdata"
-	towrap "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/trafficopswrapper"
-	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(
-	opsConfigFile string,
-	toSession towrap.ITrafficOpsSession,
-	toData todata.TODataThreadsafe,
-	opsConfigChangeSubscribers []chan<- handler.OpsConfig,
-	toChangeSubscribers []chan<- towrap.ITrafficOpsSession,
-	localStates peer.CRStatesThreadsafe,
-	peerStates peer.CRStatesPeersThreadsafe,
-	combinedStates peer.CRStatesThreadsafe,
-	statInfoHistory threadsafe.ResultInfoHistory,
-	statResultHistory threadsafe.ResultStatHistory,
-	statMaxKbpses threadsafe.CacheKbpses,
-	healthHistory threadsafe.ResultHistory,
-	lastStats threadsafe.LastStats,
-	dsStats threadsafe.DSStatsReader,
-	events health.ThreadsafeEvents,
-	staticAppData StaticAppData,
-	healthPollInterval time.Duration,
-	lastHealthDurations DurationMapThreadsafe,
-	fetchCount threadsafe.Uint,
-	healthIteration threadsafe.Uint,
-	errorCount threadsafe.Uint,
-	localCacheStatus threadsafe.CacheAvailableStatus,
-	unpolledCaches threadsafe.UnpolledCaches,
-	monitorConfig TrafficMonitorConfigMapThreadsafe,
-	cfg config.Config,
-) OpsConfigThreadsafe {
-
-	opsConfigFileChannel := make(chan interface{})
-	opsConfigFilePoller := poller.FilePoller{
-		File:          opsConfigFile,
-		ResultChannel: opsConfigFileChannel,
-	}
-
-	opsConfigChannel := make(chan handler.OpsConfig)
-	opsConfigFileHandler := handler.OpsConfigFileHandler{
-		ResultChannel:    opsConfigFilePoller.ResultChannel,
-		OpsConfigChannel: opsConfigChannel,
-	}
-
-	go opsConfigFileHandler.Listen()
-	go opsConfigFilePoller.Poll()
-
-	opsConfig := NewOpsConfigThreadsafe()
-
-	// 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() {
-		httpServer := srvhttp.Server{}
-
-		for newOpsConfig := range opsConfigChannel {
-			var err error
-			opsConfig.Set(newOpsConfig)
-
-			listenAddress := ":80" // default
-
-			if newOpsConfig.HttpListener != "" {
-				listenAddress = newOpsConfig.HttpListener
-			}
-
-			handleErr := func(err error) {
-				errorCount.Inc()
-				log.Errorf("OpsConfigManager: %v\n", err)
-			}
-
-			endpoints := MakeDispatchMap(
-				opsConfig,
-				toSession,
-				localStates,
-				peerStates,
-				combinedStates,
-				statInfoHistory,
-				statResultHistory,
-				statMaxKbpses,
-				healthHistory,
-				dsStats,
-				events,
-				staticAppData,
-				healthPollInterval,
-				lastHealthDurations,
-				fetchCount,
-				healthIteration,
-				errorCount,
-				toData,
-				localCacheStatus,
-				lastStats,
-				unpolledCaches,
-				monitorConfig,
-			)
-			err = httpServer.Run(endpoints, listenAddress, cfg.ServeReadTimeout, cfg.ServeWriteTimeout, cfg.StaticFileDir)
-			if err != nil {
-				handleErr(fmt.Errorf("MonitorConfigPoller: error creating HTTP server: %s\n", err))
-				continue
-			}
-
-			realToSession, err := to.Login(newOpsConfig.Url, newOpsConfig.Username, newOpsConfig.Password, newOpsConfig.Insecure)
-			if err != nil {
-				handleErr(fmt.Errorf("MonitorConfigPoller: error instantiating Session with traffic_ops: %s\n", err))
-				continue
-			}
-			toSession.Set(realToSession)
-
-			if err := toData.Fetch(toSession, newOpsConfig.CdnName); err != nil {
-				handleErr(fmt.Errorf("Error getting Traffic Ops data: %v\n", err))
-				continue
-			}
-
-			// These must be in a goroutine, because the monitorConfigPoller tick sends to a channel this select listens for. Thus, if we block on sends to the monitorConfigPoller, we have a livelock race condition.
-			// More generically, we're using goroutines as an infinite chan buffer, to avoid potential livelocks
-			for _, subscriber := range opsConfigChangeSubscribers {
-				go func(s chan<- handler.OpsConfig) { s <- newOpsConfig }(subscriber)
-			}
-			for _, subscriber := range toChangeSubscribers {
-				go func(s chan<- towrap.ITrafficOpsSession) { s <- toSession }(subscriber)
-			}
-		}
-	}()
-
-	return opsConfig
-}


[10/19] incubator-trafficcontrol git commit: Move TM2 to trafficcontrol/traffic_monitor_golang

Posted by ne...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/windows.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/windows.go b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/windows.go
deleted file mode 100644
index c836bdb..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/windows.go
+++ /dev/null
@@ -1,561 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build windows
-
-package fsnotify
-
-import (
-	"errors"
-	"fmt"
-	"os"
-	"path/filepath"
-	"runtime"
-	"sync"
-	"syscall"
-	"unsafe"
-)
-
-// Watcher watches a set of files, delivering events to a channel.
-type Watcher struct {
-	Events   chan Event
-	Errors   chan error
-	isClosed bool           // Set to true when Close() is first called
-	mu       sync.Mutex     // Map access
-	port     syscall.Handle // Handle to completion port
-	watches  watchMap       // Map of watches (key: i-number)
-	input    chan *input    // Inputs to the reader are sent on this channel
-	quit     chan chan<- error
-}
-
-// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
-func NewWatcher() (*Watcher, error) {
-	port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
-	if e != nil {
-		return nil, os.NewSyscallError("CreateIoCompletionPort", e)
-	}
-	w := &Watcher{
-		port:    port,
-		watches: make(watchMap),
-		input:   make(chan *input, 1),
-		Events:  make(chan Event, 50),
-		Errors:  make(chan error),
-		quit:    make(chan chan<- error, 1),
-	}
-	go w.readEvents()
-	return w, nil
-}
-
-// Close removes all watches and closes the events channel.
-func (w *Watcher) Close() error {
-	if w.isClosed {
-		return nil
-	}
-	w.isClosed = true
-
-	// Send "quit" message to the reader goroutine
-	ch := make(chan error)
-	w.quit <- ch
-	if err := w.wakeupReader(); err != nil {
-		return err
-	}
-	return <-ch
-}
-
-// Add starts watching the named file or directory (non-recursively).
-func (w *Watcher) Add(name string) error {
-	if w.isClosed {
-		return errors.New("watcher already closed")
-	}
-	in := &input{
-		op:    opAddWatch,
-		path:  filepath.Clean(name),
-		flags: sysFSALLEVENTS,
-		reply: make(chan error),
-	}
-	w.input <- in
-	if err := w.wakeupReader(); err != nil {
-		return err
-	}
-	return <-in.reply
-}
-
-// Remove stops watching the the named file or directory (non-recursively).
-func (w *Watcher) Remove(name string) error {
-	in := &input{
-		op:    opRemoveWatch,
-		path:  filepath.Clean(name),
-		reply: make(chan error),
-	}
-	w.input <- in
-	if err := w.wakeupReader(); err != nil {
-		return err
-	}
-	return <-in.reply
-}
-
-const (
-	// Options for AddWatch
-	sysFSONESHOT = 0x80000000
-	sysFSONLYDIR = 0x1000000
-
-	// Events
-	sysFSACCESS     = 0x1
-	sysFSALLEVENTS  = 0xfff
-	sysFSATTRIB     = 0x4
-	sysFSCLOSE      = 0x18
-	sysFSCREATE     = 0x100
-	sysFSDELETE     = 0x200
-	sysFSDELETESELF = 0x400
-	sysFSMODIFY     = 0x2
-	sysFSMOVE       = 0xc0
-	sysFSMOVEDFROM  = 0x40
-	sysFSMOVEDTO    = 0x80
-	sysFSMOVESELF   = 0x800
-
-	// Special events
-	sysFSIGNORED   = 0x8000
-	sysFSQOVERFLOW = 0x4000
-)
-
-func newEvent(name string, mask uint32) Event {
-	e := Event{Name: name}
-	if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
-		e.Op |= Create
-	}
-	if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
-		e.Op |= Remove
-	}
-	if mask&sysFSMODIFY == sysFSMODIFY {
-		e.Op |= Write
-	}
-	if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
-		e.Op |= Rename
-	}
-	if mask&sysFSATTRIB == sysFSATTRIB {
-		e.Op |= Chmod
-	}
-	return e
-}
-
-const (
-	opAddWatch = iota
-	opRemoveWatch
-)
-
-const (
-	provisional uint64 = 1 << (32 + iota)
-)
-
-type input struct {
-	op    int
-	path  string
-	flags uint32
-	reply chan error
-}
-
-type inode struct {
-	handle syscall.Handle
-	volume uint32
-	index  uint64
-}
-
-type watch struct {
-	ov     syscall.Overlapped
-	ino    *inode            // i-number
-	path   string            // Directory path
-	mask   uint64            // Directory itself is being watched with these notify flags
-	names  map[string]uint64 // Map of names being watched and their notify flags
-	rename string            // Remembers the old name while renaming a file
-	buf    [4096]byte
-}
-
-type indexMap map[uint64]*watch
-type watchMap map[uint32]indexMap
-
-func (w *Watcher) wakeupReader() error {
-	e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
-	if e != nil {
-		return os.NewSyscallError("PostQueuedCompletionStatus", e)
-	}
-	return nil
-}
-
-func getDir(pathname string) (dir string, err error) {
-	attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
-	if e != nil {
-		return "", os.NewSyscallError("GetFileAttributes", e)
-	}
-	if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
-		dir = pathname
-	} else {
-		dir, _ = filepath.Split(pathname)
-		dir = filepath.Clean(dir)
-	}
-	return
-}
-
-func getIno(path string) (ino *inode, err error) {
-	h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
-		syscall.FILE_LIST_DIRECTORY,
-		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
-		nil, syscall.OPEN_EXISTING,
-		syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
-	if e != nil {
-		return nil, os.NewSyscallError("CreateFile", e)
-	}
-	var fi syscall.ByHandleFileInformation
-	if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
-		syscall.CloseHandle(h)
-		return nil, os.NewSyscallError("GetFileInformationByHandle", e)
-	}
-	ino = &inode{
-		handle: h,
-		volume: fi.VolumeSerialNumber,
-		index:  uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
-	}
-	return ino, nil
-}
-
-// Must run within the I/O thread.
-func (m watchMap) get(ino *inode) *watch {
-	if i := m[ino.volume]; i != nil {
-		return i[ino.index]
-	}
-	return nil
-}
-
-// Must run within the I/O thread.
-func (m watchMap) set(ino *inode, watch *watch) {
-	i := m[ino.volume]
-	if i == nil {
-		i = make(indexMap)
-		m[ino.volume] = i
-	}
-	i[ino.index] = watch
-}
-
-// Must run within the I/O thread.
-func (w *Watcher) addWatch(pathname string, flags uint64) error {
-	dir, err := getDir(pathname)
-	if err != nil {
-		return err
-	}
-	if flags&sysFSONLYDIR != 0 && pathname != dir {
-		return nil
-	}
-	ino, err := getIno(dir)
-	if err != nil {
-		return err
-	}
-	w.mu.Lock()
-	watchEntry := w.watches.get(ino)
-	w.mu.Unlock()
-	if watchEntry == nil {
-		if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
-			syscall.CloseHandle(ino.handle)
-			return os.NewSyscallError("CreateIoCompletionPort", e)
-		}
-		watchEntry = &watch{
-			ino:   ino,
-			path:  dir,
-			names: make(map[string]uint64),
-		}
-		w.mu.Lock()
-		w.watches.set(ino, watchEntry)
-		w.mu.Unlock()
-		flags |= provisional
-	} else {
-		syscall.CloseHandle(ino.handle)
-	}
-	if pathname == dir {
-		watchEntry.mask |= flags
-	} else {
-		watchEntry.names[filepath.Base(pathname)] |= flags
-	}
-	if err = w.startRead(watchEntry); err != nil {
-		return err
-	}
-	if pathname == dir {
-		watchEntry.mask &= ^provisional
-	} else {
-		watchEntry.names[filepath.Base(pathname)] &= ^provisional
-	}
-	return nil
-}
-
-// Must run within the I/O thread.
-func (w *Watcher) remWatch(pathname string) error {
-	dir, err := getDir(pathname)
-	if err != nil {
-		return err
-	}
-	ino, err := getIno(dir)
-	if err != nil {
-		return err
-	}
-	w.mu.Lock()
-	watch := w.watches.get(ino)
-	w.mu.Unlock()
-	if watch == nil {
-		return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
-	}
-	if pathname == dir {
-		w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
-		watch.mask = 0
-	} else {
-		name := filepath.Base(pathname)
-		w.sendEvent(watch.path+"\\"+name, watch.names[name]&sysFSIGNORED)
-		delete(watch.names, name)
-	}
-	return w.startRead(watch)
-}
-
-// Must run within the I/O thread.
-func (w *Watcher) deleteWatch(watch *watch) {
-	for name, mask := range watch.names {
-		if mask&provisional == 0 {
-			w.sendEvent(watch.path+"\\"+name, mask&sysFSIGNORED)
-		}
-		delete(watch.names, name)
-	}
-	if watch.mask != 0 {
-		if watch.mask&provisional == 0 {
-			w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
-		}
-		watch.mask = 0
-	}
-}
-
-// Must run within the I/O thread.
-func (w *Watcher) startRead(watch *watch) error {
-	if e := syscall.CancelIo(watch.ino.handle); e != nil {
-		w.Errors <- os.NewSyscallError("CancelIo", e)
-		w.deleteWatch(watch)
-	}
-	mask := toWindowsFlags(watch.mask)
-	for _, m := range watch.names {
-		mask |= toWindowsFlags(m)
-	}
-	if mask == 0 {
-		if e := syscall.CloseHandle(watch.ino.handle); e != nil {
-			w.Errors <- os.NewSyscallError("CloseHandle", e)
-		}
-		w.mu.Lock()
-		delete(w.watches[watch.ino.volume], watch.ino.index)
-		w.mu.Unlock()
-		return nil
-	}
-	e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
-		uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
-	if e != nil {
-		err := os.NewSyscallError("ReadDirectoryChanges", e)
-		if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
-			// Watched directory was probably removed
-			if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) {
-				if watch.mask&sysFSONESHOT != 0 {
-					watch.mask = 0
-				}
-			}
-			err = nil
-		}
-		w.deleteWatch(watch)
-		w.startRead(watch)
-		return err
-	}
-	return nil
-}
-
-// readEvents reads from the I/O completion port, converts the
-// received events into Event objects and sends them via the Events channel.
-// Entry point to the I/O thread.
-func (w *Watcher) readEvents() {
-	var (
-		n, key uint32
-		ov     *syscall.Overlapped
-	)
-	runtime.LockOSThread()
-
-	for {
-		e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
-		watch := (*watch)(unsafe.Pointer(ov))
-
-		if watch == nil {
-			select {
-			case ch := <-w.quit:
-				w.mu.Lock()
-				var indexes []indexMap
-				for _, index := range w.watches {
-					indexes = append(indexes, index)
-				}
-				w.mu.Unlock()
-				for _, index := range indexes {
-					for _, watch := range index {
-						w.deleteWatch(watch)
-						w.startRead(watch)
-					}
-				}
-				var err error
-				if e := syscall.CloseHandle(w.port); e != nil {
-					err = os.NewSyscallError("CloseHandle", e)
-				}
-				close(w.Events)
-				close(w.Errors)
-				ch <- err
-				return
-			case in := <-w.input:
-				switch in.op {
-				case opAddWatch:
-					in.reply <- w.addWatch(in.path, uint64(in.flags))
-				case opRemoveWatch:
-					in.reply <- w.remWatch(in.path)
-				}
-			default:
-			}
-			continue
-		}
-
-		switch e {
-		case syscall.ERROR_MORE_DATA:
-			if watch == nil {
-				w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
-			} else {
-				// The i/o succeeded but the buffer is full.
-				// In theory we should be building up a full packet.
-				// In practice we can get away with just carrying on.
-				n = uint32(unsafe.Sizeof(watch.buf))
-			}
-		case syscall.ERROR_ACCESS_DENIED:
-			// Watched directory was probably removed
-			w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
-			w.deleteWatch(watch)
-			w.startRead(watch)
-			continue
-		case syscall.ERROR_OPERATION_ABORTED:
-			// CancelIo was called on this handle
-			continue
-		default:
-			w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
-			continue
-		case nil:
-		}
-
-		var offset uint32
-		for {
-			if n == 0 {
-				w.Events <- newEvent("", sysFSQOVERFLOW)
-				w.Errors <- errors.New("short read in readEvents()")
-				break
-			}
-
-			// Point "raw" to the event in the buffer
-			raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
-			buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
-			name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
-			fullname := watch.path + "\\" + name
-
-			var mask uint64
-			switch raw.Action {
-			case syscall.FILE_ACTION_REMOVED:
-				mask = sysFSDELETESELF
-			case syscall.FILE_ACTION_MODIFIED:
-				mask = sysFSMODIFY
-			case syscall.FILE_ACTION_RENAMED_OLD_NAME:
-				watch.rename = name
-			case syscall.FILE_ACTION_RENAMED_NEW_NAME:
-				if watch.names[watch.rename] != 0 {
-					watch.names[name] |= watch.names[watch.rename]
-					delete(watch.names, watch.rename)
-					mask = sysFSMOVESELF
-				}
-			}
-
-			sendNameEvent := func() {
-				if w.sendEvent(fullname, watch.names[name]&mask) {
-					if watch.names[name]&sysFSONESHOT != 0 {
-						delete(watch.names, name)
-					}
-				}
-			}
-			if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
-				sendNameEvent()
-			}
-			if raw.Action == syscall.FILE_ACTION_REMOVED {
-				w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
-				delete(watch.names, name)
-			}
-			if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
-				if watch.mask&sysFSONESHOT != 0 {
-					watch.mask = 0
-				}
-			}
-			if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
-				fullname = watch.path + "\\" + watch.rename
-				sendNameEvent()
-			}
-
-			// Move to the next event in the buffer
-			if raw.NextEntryOffset == 0 {
-				break
-			}
-			offset += raw.NextEntryOffset
-
-			// Error!
-			if offset >= n {
-				w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
-				break
-			}
-		}
-
-		if err := w.startRead(watch); err != nil {
-			w.Errors <- err
-		}
-	}
-}
-
-func (w *Watcher) sendEvent(name string, mask uint64) bool {
-	if mask == 0 {
-		return false
-	}
-	event := newEvent(name, uint32(mask))
-	select {
-	case ch := <-w.quit:
-		w.quit <- ch
-	case w.Events <- event:
-	}
-	return true
-}
-
-func toWindowsFlags(mask uint64) uint32 {
-	var m uint32
-	if mask&sysFSACCESS != 0 {
-		m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
-	}
-	if mask&sysFSMODIFY != 0 {
-		m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
-	}
-	if mask&sysFSATTRIB != 0 {
-		m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
-	}
-	if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
-		m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
-	}
-	return m
-}
-
-func toFSnotifyFlags(action uint32) uint64 {
-	switch action {
-	case syscall.FILE_ACTION_ADDED:
-		return sysFSCREATE
-	case syscall.FILE_ACTION_REMOVED:
-		return sysFSDELETE
-	case syscall.FILE_ACTION_MODIFIED:
-		return sysFSMODIFY
-	case syscall.FILE_ACTION_RENAMED_OLD_NAME:
-		return sysFSMOVEDFROM
-	case syscall.FILE_ACTION_RENAMED_NEW_NAME:
-		return sysFSMOVEDTO
-	}
-	return 0
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/build/build_rpm.sh
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/build/build_rpm.sh b/traffic_monitor_golang/build/build_rpm.sh
new file mode 100755
index 0000000..9ec72a3
--- /dev/null
+++ b/traffic_monitor_golang/build/build_rpm.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+
+#
+#
+# Licensed 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.
+#
+
+function importFunctions() {
+	local script=$(readlink -f "$0")
+	local scriptdir=$(dirname "$script")
+	export TM_DIR=$(dirname "$scriptdir")
+	export TC_DIR=$(dirname "$TM_DIR")
+	functions_sh="$TC_DIR/build/functions.sh"
+	if [[ ! -r $functions_sh ]]; then
+		echo "error: can't find $functions_sh"
+		exit 1
+	fi
+	. "$functions_sh"
+}
+
+#----------------------------------------
+function initBuildArea() {
+	echo "Initializing the build area."
+	mkdir -p "$RPMBUILD"/{SPECS,SOURCES,RPMS,SRPMS,BUILD,BUILDROOT} || { echo "Could not create $RPMBUILD: $?"; exit 1; }
+
+	# tar/gzip the source
+	local tm_dest=$(createSourceDir traffic_monitor)
+	cd "$TM_DIR" || \
+		 { echo "Could not cd to $TM_DIR: $?"; exit 1; }
+	rsync -av ./ "$tm_dest"/ || \
+		 { echo "Could not copy to $tm_dest: $?"; exit 1; }
+	cp "$TM_DIR"/build/*.spec "$RPMBUILD"/SPECS/. || \
+		 { echo "Could not copy spec files: $?"; exit 1; }
+
+	cp -r "$TM_DIR"/ "$tm_dest" || { echo "Could not copy $TM_DIR to $tm_dest: $?"; exit 1; }
+
+	tar -czvf "$tm_dest".tgz -C "$RPMBUILD"/SOURCES $(basename $tm_dest) || { echo "Could not create tar archive $tm_dest.tgz: $?"; exit 1; }
+	cp "$TM_DIR"/build/*.spec "$RPMBUILD"/SPECS/. || { echo "Could not copy spec files: $?"; exit 1; }
+
+	echo "The build area has been initialized."
+}
+
+# ---------------------------------------
+
+importFunctions
+checkEnvironment go
+initBuildArea
+buildRpm traffic_monitor

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/build/traffic_monitor.init
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/build/traffic_monitor.init b/traffic_monitor_golang/build/traffic_monitor.init
new file mode 100644
index 0000000..9240ead
--- /dev/null
+++ b/traffic_monitor_golang/build/traffic_monitor.init
@@ -0,0 +1,119 @@
+#!/bin/bash
+
+# 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.
+
+# Startup script for traffic_monitor
+#
+#
+# chkconfig: 345 99 10
+# description: traffic_monitor control script
+# processname: traffic_monitor
+
+### BEGIN INIT INFO
+# Provides: traffic_monitor
+# Required-Start: $network $local_fs $syslog
+# Required-Stop: $network $local_fs $syslog
+# Default-Start: 3 4 5
+# Default-Stop: 0 1 2 6
+# Short-Description: start and stop Traffic Monitor
+# Description: Controls all traffic monitor processes at once.
+### END INIT INFO
+
+# Source function library.
+. /etc/init.d/functions
+
+# Source networking configuration.
+. /etc/sysconfig/network
+
+basepath=/opt/traffic_monitor
+binpath=bin
+runpath=var/run
+name=traffic_monitor
+prog=$basepath/$binpath/$name
+lockfile=$basepath/$runpath/$name
+options="--opsCfg /opt/traffic_monitor/conf/traffic_ops.cfg --config /opt/traffic_monitor/conf/traffic_monitor.cfg"
+
+start() {
+        [ "$NETWORKING" = "no" ] && exit 1
+        [ -x $prog ] || exit 5
+
+        #Set file limits
+        # Max open files
+        OPEN_FILE_LIMIT=65536
+        ulimit -n $OPEN_FILE_LIMIT
+        if [ $? -ne 0 ]; then
+            echo -n "Failed to set open file limit to $OPEN_FILE_LIMIT"
+            exit 1
+        fi
+
+        # Start daemons.
+        echo -n $"Starting $name: "
+        daemon nohup $prog $options < /dev/null > /opt/traffic_monitor/var/log/traffic_monitor.log 2>&1 &
+        RETVAL=$?
+        echo
+        [ $RETVAL -eq 0 ] && touch $lockfile
+        return $RETVAL
+}
+
+stop() {
+        echo -n $"Shutting down $name: "
+        killproc $prog
+        RETVAL=$?
+        echo
+        [ $RETVAL -eq 0 ] && rm -f $lockfile
+        return $RETVAL
+}
+
+reload() {
+        echo -n $"Reloading $name: "
+        if [ -n "`pidofproc $prog`" ]; then
+                killproc $prog -HUP
+        else
+                failure $"Reloading $name"
+        fi
+        RETVAL=$?
+        echo
+}
+
+case "$1" in
+  start)
+        start
+        ;;
+  stop)
+        stop
+        ;;
+  status)
+        status $prog
+        ;;
+  restart|force-reload)
+        stop
+        start
+        ;;
+  try-restart|condrestart)
+        if status $prog > /dev/null; then
+            stop
+            start
+        fi
+        ;;
+  reload)
+        reload
+        ;;
+  *)
+        echo $"Usage: $0 {start|stop|status|restart|try-restart|reload|force-reload}"
+        exit 2
+esac

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/build/traffic_monitor.logrotate
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/build/traffic_monitor.logrotate b/traffic_monitor_golang/build/traffic_monitor.logrotate
new file mode 100644
index 0000000..e7073ad
--- /dev/null
+++ b/traffic_monitor_golang/build/traffic_monitor.logrotate
@@ -0,0 +1,26 @@
+# 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.
+
+/opt/traffic_monitor/var/log/traffic_monitor.log {
+        compress
+        maxage 30
+        missingok
+        nomail
+        size 10M
+        rotate 5
+        copytruncate
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/build/traffic_monitor.spec
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/build/traffic_monitor.spec b/traffic_monitor_golang/build/traffic_monitor.spec
new file mode 100644
index 0000000..043d977
--- /dev/null
+++ b/traffic_monitor_golang/build/traffic_monitor.spec
@@ -0,0 +1,155 @@
+#
+#
+# Licensed 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.
+#
+#
+# RPM spec file for the Go version of Traffic Monitor (tm).
+#
+%define debug_package %{nil}
+Name:		traffic_monitor
+Version:        %{traffic_control_version}
+Release:        %{build_number}
+Summary:	Monitor the caches
+Packager:	david_neuman2 at Cable dot Comcast dot com
+Vendor:		Apache Software Foundation
+Group:		Applications/Communications
+License:	Apache License, Version 2.0
+URL:		https://github.com/apache/incubator-trafficcontrol
+Source:		%{_sourcedir}/traffic_monitor-%{traffic_control_version}.tgz
+
+%description
+Installs traffic_monitor
+
+%prep
+
+%setup
+
+%build
+export GOPATH=$(pwd)
+# Create build area with proper gopath structure
+mkdir -p src pkg bin || { echo "Could not create directories in $(pwd): $!"; exit 1; }
+
+go_get_version() {
+  local src=$1
+  local version=$2
+  (
+   cd $src && \
+   git checkout $version && \
+   go get -v \
+  )
+}
+
+# get traffic_ops client
+godir=src/github.com/apache/incubator-trafficcontrol/traffic_ops/client
+( mkdir -p "$godir" && \
+  cd "$godir" && \
+  cp -r "$TC_DIR"/traffic_ops/client/* . && \
+  go get -v \
+) || { echo "Could not build go program at $(pwd): $!"; exit 1; }
+
+#build traffic_monitor binary
+godir=src/github.com/apache/incubator-trafficcontrol/traffic_monitor_golang
+oldpwd=$(pwd)
+( mkdir -p "$godir" && \
+  cd "$godir" && \
+  cp -r "$TC_DIR"/traffic_monitor_golang/* . && \
+  cd traffic_monitor && \
+  go get -d -v && \
+  go build -ldflags "-X main.GitRevision=`git rev-parse HEAD` -X main.BuildTimestamp=`date +'%Y-%M-%dT%H:%M:%s'`" \
+) || { echo "Could not build go program at $(pwd): $!"; exit 1; }
+
+%install
+mkdir -p "${RPM_BUILD_ROOT}"/opt/traffic_monitor
+mkdir -p "${RPM_BUILD_ROOT}"/opt/traffic_monitor/bin
+mkdir -p "${RPM_BUILD_ROOT}"/opt/traffic_monitor/conf
+mkdir -p "${RPM_BUILD_ROOT}"/opt/traffic_monitor/backup
+mkdir -p "${RPM_BUILD_ROOT}"/opt/traffic_monitor/static
+mkdir -p "${RPM_BUILD_ROOT}"/opt/traffic_monitor/var/run
+mkdir -p "${RPM_BUILD_ROOT}"/opt/traffic_monitor/var/log
+mkdir -p "${RPM_BUILD_ROOT}"/etc/init.d
+mkdir -p "${RPM_BUILD_ROOT}"/etc/logrotate.d
+
+src=src/github.com/apache/incubator-trafficcontrol/traffic_monitor_golang
+cp -p "$src"/traffic_monitor/traffic_monitor     "${RPM_BUILD_ROOT}"/opt/traffic_monitor/bin/traffic_monitor
+cp  "$src"/traffic_monitor/static/index.html     "${RPM_BUILD_ROOT}"/opt/traffic_monitor/static/index.html
+cp  "$src"/traffic_monitor/static/sorttable.js     "${RPM_BUILD_ROOT}"/opt/traffic_monitor/static/sorttable.js
+cp "$src"/conf/traffic_ops.cfg        "${RPM_BUILD_ROOT}"/opt/traffic_monitor/conf/traffic_ops.cfg
+cp "$src"/conf/traffic_monitor.cfg        "${RPM_BUILD_ROOT}"/opt/traffic_monitor/conf/traffic_monitor.cfg
+cp "$src"/build/traffic_monitor.init       "${RPM_BUILD_ROOT}"/etc/init.d/traffic_monitor
+cp "$src"/build/traffic_monitor.logrotate  "${RPM_BUILD_ROOT}"/etc/logrotate.d/traffic_monitor
+
+%pre
+/usr/bin/getent group traffic_monitor >/dev/null
+
+if [ $? -ne 0 ]; then
+
+	/usr/sbin/groupadd -g 423 traffic_monitor
+fi
+
+/usr/bin/getent passwd traffic_monitor >/dev/null
+
+if [ $? -ne 0 ]; then
+
+	/usr/sbin/useradd -g traffic_monitor -u 423 -d /opt/traffic_monitor -M traffic_monitor
+
+fi
+
+/usr/bin/passwd -l traffic_monitor >/dev/null
+/usr/bin/chage -E -1 -I -1 -m 0 -M 99999 -W 7 traffic_monitor
+
+if [ -e /etc/init.d/traffic_monitor ]; then
+	/sbin/service traffic_monitor stop
+fi
+
+#don't install over the top of java TM.  This is a workaround since yum doesn't respect the Conflicts tag.
+if [[ $(rpm -q traffic_monitor --qf "%{VERSION}-%{RELEASE}") < 1.9.0 ]]
+then
+    echo -e "\n****************\n"
+    echo "A java version of traffic_monitor is installed.  Please backup/remove that version before installing the golang version of traffic_monitor."
+    echo -e "\n****************\n"
+    exit 1
+fi
+
+%post
+
+/sbin/chkconfig --add traffic_monitor
+/sbin/chkconfig traffic_monitor on
+
+%files
+%defattr(644, traffic_monitor, traffic_monitor, 755)
+%config(noreplace) /opt/traffic_monitor/conf/traffic_monitor.cfg
+%config(noreplace) /opt/traffic_monitor/conf/traffic_ops.cfg
+%config(noreplace) /etc/logrotate.d/traffic_monitor
+
+%dir /opt/traffic_monitor
+%dir /opt/traffic_monitor/bin
+%dir /opt/traffic_monitor/conf
+%dir /opt/traffic_monitor/backup
+%dir /opt/traffic_monitor/static
+%dir /opt/traffic_monitor/var
+%dir /opt/traffic_monitor/var/log
+%dir /opt/traffic_monitor/var/run
+
+%attr(600, traffic_monitor, traffic_monitor) /opt/traffic_monitor/conf/*
+%attr(600, traffic_monitor, traffic_monitor) /opt/traffic_monitor/static/*
+%attr(755, traffic_monitor, traffic_monitor) /opt/traffic_monitor/bin/*
+%attr(755, traffic_monitor, traffic_monitor) /etc/init.d/traffic_monitor
+
+%preun
+# args for hooks: http://www.ibm.com/developerworks/library/l-rpm2/
+# if $1 = 0, this is an uninstallation, if $1 = 1, this is an upgrade (don't do anything)
+if [ "$1" = "0" ]; then
+	/sbin/chkconfig traffic_monitor off
+	/etc/init.d/traffic_monitor stop
+	/sbin/chkconfig --del traffic_monitor
+fi

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/common/adapter/adapter.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/common/adapter/adapter.go b/traffic_monitor_golang/common/adapter/adapter.go
new file mode 100644
index 0000000..bdb3621
--- /dev/null
+++ b/traffic_monitor_golang/common/adapter/adapter.go
@@ -0,0 +1,29 @@
+package adapter
+
+/*
+ * 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 (
+	"io"
+)
+
+type Adapter interface {
+	Transform(io.Reader) (interface{}, error)
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/common/crstates/crstates.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/common/crstates/crstates.go b/traffic_monitor_golang/common/crstates/crstates.go
new file mode 100644
index 0000000..20e10ca
--- /dev/null
+++ b/traffic_monitor_golang/common/crstates/crstates.go
@@ -0,0 +1,37 @@
+package crstates
+
+/*
+ * 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.
+ */
+
+
+type Cache struct {
+	Name      string `json:"name,omitempty"`
+	Available bool   `json:"isAvailable"`
+}
+
+type DeliveryService struct {
+	Name              string   `json:"name,omitempty"`
+	DisabledLocations []string `json:"disabledLocations"`
+	Available         bool     `json:"isAvailable"`
+}
+
+type CRStates struct {
+	Caches           map[string]Cache           `json:"caches"`
+	DeliveryServices map[string]DeliveryService `json:"deliveryServices"`
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/common/fetcher/fetcher.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/common/fetcher/fetcher.go b/traffic_monitor_golang/common/fetcher/fetcher.go
new file mode 100644
index 0000000..75b4295
--- /dev/null
+++ b/traffic_monitor_golang/common/fetcher/fetcher.go
@@ -0,0 +1,100 @@
+package fetcher
+
+/*
+ * 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"
+	"io/ioutil"
+	"net/http"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/handler"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
+	"github.com/davecheney/gmx"
+)
+
+type Fetcher interface {
+	Fetch(string, string, uint64, chan<- uint64)
+}
+
+type HttpFetcher struct {
+	Client  *http.Client
+	Headers map[string]string
+	Handler handler.Handler
+	Counters
+}
+
+type Result struct {
+	Source string
+	Data   []byte
+	Error  error
+}
+
+type Counters struct {
+	Success *gmx.Counter
+	Fail    *gmx.Counter
+	Pending *gmx.Gauge
+}
+
+func (f HttpFetcher) Fetch(id string, url string, pollId uint64, pollFinishedChan chan<- uint64) {
+	log.Debugf("poll %v %v fetch start\n", pollId, time.Now())
+	req, err := http.NewRequest("GET", url, nil)
+	// TODO: change this to use f.Headers. -jse
+	req.Header.Set("User-Agent", "traffic_monitor/1.0") // TODO change to 2.0?
+	req.Header.Set("Connection", "keep-alive")
+	if f.Pending != nil {
+		f.Pending.Inc()
+	}
+	startReq := time.Now()
+	response, err := f.Client.Do(req)
+	reqTime := time.Now().Sub(startReq)
+	if f.Pending != nil {
+		f.Pending.Dec()
+	}
+	defer func() {
+		if response != nil && response.Body != nil {
+			ioutil.ReadAll(response.Body) // TODO determine if necessary
+			response.Body.Close()
+		}
+	}()
+
+	if err == nil && response == nil {
+		err = fmt.Errorf("err nil and response nil")
+	}
+	if err == nil && response != nil && (response.StatusCode < 200 || response.StatusCode > 299) {
+		err = fmt.Errorf("bad status: %v", response.StatusCode)
+	}
+	if err != nil {
+		err = fmt.Errorf("id %v url %v fetch error: %v", id, url, err)
+	}
+
+	if err == nil && response != nil {
+		if f.Success != nil {
+			f.Success.Inc()
+		}
+		log.Debugf("poll %v %v fetch end\n", pollId, time.Now())
+		f.Handler.Handle(id, response.Body, reqTime, err, pollId, pollFinishedChan)
+	} else {
+		if f.Fail != nil {
+			f.Fail.Inc()
+		}
+		f.Handler.Handle(id, nil, reqTime, err, pollId, pollFinishedChan)
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/common/handler/handler.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/common/handler/handler.go b/traffic_monitor_golang/common/handler/handler.go
new file mode 100644
index 0000000..e4dd84c
--- /dev/null
+++ b/traffic_monitor_golang/common/handler/handler.go
@@ -0,0 +1,68 @@
+package handler
+
+/*
+ * 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"
+	"io"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
+)
+
+const (
+	NOTIFY_NEVER = iota
+	NOTIFY_CHANGE
+	NOTIFY_ALWAYS
+)
+
+type Handler interface {
+	Handle(string, io.Reader, time.Duration, error, uint64, chan<- uint64)
+}
+
+type OpsConfigFileHandler struct {
+	Content          interface{}
+	ResultChannel    chan interface{}
+	OpsConfigChannel chan OpsConfig
+}
+
+type OpsConfig struct {
+	Username     string `json:"username"`
+	Password     string `json:"password"`
+	Url          string `json:"url"`
+	Insecure     bool   `json:"insecure"`
+	CdnName      string `json:"cdnName"`
+	HttpListener string `json:"httpListener"`
+}
+
+func (handler OpsConfigFileHandler) Listen() {
+	for {
+		result := <-handler.ResultChannel
+		var toc OpsConfig
+
+		err := json.Unmarshal(result.([]byte), &toc)
+
+		if err != nil {
+			log.Errorf("Could not unmarshal Ops Config JSON: %s\n", err)
+		} else {
+			handler.OpsConfigChannel <- toc
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/common/instrumentation/instrumentation.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/common/instrumentation/instrumentation.go b/traffic_monitor_golang/common/instrumentation/instrumentation.go
new file mode 100644
index 0000000..12e29b4
--- /dev/null
+++ b/traffic_monitor_golang/common/instrumentation/instrumentation.go
@@ -0,0 +1,31 @@
+package instrumentation
+
+/*
+ * 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 (
+	"github.com/davecheney/gmx"
+)
+
+var TimerFail *gmx.Counter
+
+func init() {
+	TimerFail = gmx.NewCounter("timerFail")
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/common/log/log.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/common/log/log.go b/traffic_monitor_golang/common/log/log.go
new file mode 100644
index 0000000..b3b567b
--- /dev/null
+++ b/traffic_monitor_golang/common/log/log.go
@@ -0,0 +1,112 @@
+// Inspired by https://www.goinggo.net/2013/11/using-log-package-in-go.html
+package log
+
+/*
+ * 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"
+	"io"
+	"log"
+	"time"
+)
+
+var (
+	Debug   *log.Logger
+	Info    *log.Logger
+	Warning *log.Logger
+	Error   *log.Logger
+	Event   *log.Logger
+)
+
+func Init(eventW, errW, warnW, infoW, debugW io.Writer) {
+	Debug = log.New(debugW, "DEBUG: ", log.Lshortfile)
+	Info = log.New(infoW, "INFO: ", log.Lshortfile)
+	Warning = log.New(warnW, "WARNING: ", log.Lshortfile)
+	Error = log.New(errW, "ERROR: ", log.Lshortfile)
+	Event = log.New(eventW, "", 0)
+}
+
+const timeFormat = time.RFC3339Nano
+const stackFrame = 3
+
+func Errorf(format string, v ...interface{}) {
+	Error.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintf(format, v...))
+}
+func Errorln(v ...interface{}) {
+	Error.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintln(v...))
+}
+func Warnf(format string, v ...interface{}) {
+	Warning.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintf(format, v...))
+}
+func Warnln(v ...interface{}) {
+	Warning.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintln(v...))
+}
+func Infof(format string, v ...interface{}) {
+	Info.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintf(format, v...))
+}
+func Infoln(v ...interface{}) {
+	Info.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintln(v...))
+}
+func Debugf(format string, v ...interface{}) {
+	Debug.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintf(format, v...))
+}
+func Debugln(v ...interface{}) {
+	Debug.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintln(v...))
+}
+
+// event log entries (TM event.log, TR access.log, etc)
+func Eventf(t time.Time, format string, v ...interface{}) {
+	// 1484001185.287 ...
+	Event.Printf("%.3f %s", float64(t.Unix())+(float64(t.Nanosecond())/1e9), fmt.Sprintf(format, v...))
+}
+
+// Close calls `Close()` on the given Closer, and logs any error. On error, the context is logged, followed by a colon, the error message, and a newline. This is primarily designed to be used in `defer`, for example, `defer log.Close(resp.Body, "readData fetching /foo/bar")`.
+func Close(c io.Closer, context string) {
+	err := c.Close()
+	if err != nil {
+		Errorf("%v: %v", context, err)
+	}
+}
+
+// Closef acts like Close, with a given format string and values, followed by a colon, the error message, and a newline. The given values are not coerced, concatenated, or printed unless an error occurs, so this is more efficient than `Close()`.
+func Closef(c io.Closer, contextFormat string, v ...interface{}) {
+	err := c.Close()
+	if err != nil {
+		Errorf(contextFormat, v...)
+		Errorf(": %v", err)
+	}
+}
+
+// Write calls `Write()` on the given Writer, and logs any error. On error, the context is logged, followed by a colon, the error message, and a newline.
+func Write(w io.Writer, b []byte, context string) {
+	_, err := w.Write(b)
+	if err != nil {
+		Errorf("%v: %v", context, err)
+	}
+}
+
+// Writef acts like Write, with a given format string and values, followed by a colon, the error message, and a newline. The given values are not coerced, concatenated, or printed unless an error occurs, so this is more efficient than `Write()`.
+func Writef(w io.Writer, b []byte, contextFormat string, v ...interface{}) {
+	_, err := w.Write(b)
+	if err != nil {
+		Errorf(contextFormat, v...)
+		Errorf(": %v", err)
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/common/poller/heap.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/common/poller/heap.go b/traffic_monitor_golang/common/poller/heap.go
new file mode 100644
index 0000000..e15d61b
--- /dev/null
+++ b/traffic_monitor_golang/common/poller/heap.go
@@ -0,0 +1,106 @@
+package poller
+
+/*
+ * 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 (
+	"sync"
+	"time"
+)
+
+type HeapPollInfo struct {
+	Info HTTPPollInfo
+	Next time.Time
+}
+
+// Heap implements a Heap from Introduction to Algorithms (Cormen et al). A Heap allows fase access of the maximum object, in this case the latest Next time, and O(log(n)) insert. This Heap is specifically designed to be used as a Priority Queue.
+type Heap struct {
+	m        sync.Mutex
+	info     []HeapPollInfo
+	PollerID int64
+}
+
+func left(i int) int {
+	return 2*i + 1
+}
+
+func right(i int) int {
+	return 2*i + 2
+}
+
+// TODO benchmark directly replacing this, to see if Go inlines the function call
+func parent(i int) int {
+	return (i - 1) / 2
+}
+
+func (h *Heap) heapify(i int) {
+	l := left(i)
+	r := right(i)
+	var largest int
+	if l < len(h.info) && h.info[i].Next.After(h.info[l].Next) {
+		largest = l
+	} else {
+		largest = i
+	}
+
+	if r < len(h.info) && h.info[largest].Next.After(h.info[r].Next) {
+		largest = r
+	}
+
+	if largest != i {
+		h.info[i], h.info[largest] = h.info[largest], h.info[i]
+		h.heapify(largest)
+	}
+}
+
+func (h *Heap) increaseKey(i int, key HeapPollInfo) {
+	if h.info[i].Next.After(key.Next) {
+		panic("Poll.Heap.increaseKey got key smaller than index")
+	}
+
+	h.info[i] = key
+
+	for i > 0 && h.info[parent(i)].Next.After(h.info[i].Next) {
+		h.info[i], h.info[parent(i)] = h.info[parent(i)], h.info[i]
+		i = parent(i)
+	}
+}
+
+// Pop gets the latest time from the heap. Implements Algorithms HEAP-EXTRACT-MAX.
+// Returns the info with the latest time, and false if the heap is empty.
+func (h *Heap) Pop() (HeapPollInfo, bool) {
+	h.m.Lock()
+	defer h.m.Unlock()
+	if len(h.info) == 0 {
+		return HeapPollInfo{}, false
+	}
+	max := h.info[0]
+	h.info[0] = h.info[len(h.info)-1]
+	h.info = h.info[:len(h.info)-1]
+	h.heapify(0)
+	return max, true
+}
+
+// Pop gets the latest time from the heap. Implements Algorithms MAX-HEAP-INSERT.
+func (h *Heap) Push(key HeapPollInfo) {
+	h.m.Lock()
+	defer h.m.Unlock()
+	h.info = append(h.info, HeapPollInfo{Next: time.Unix(1<<63-1, 0)})
+	h.increaseKey(len(h.info)-1, key)
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/common/poller/heap_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/common/poller/heap_test.go b/traffic_monitor_golang/common/poller/heap_test.go
new file mode 100644
index 0000000..f943c96
--- /dev/null
+++ b/traffic_monitor_golang/common/poller/heap_test.go
@@ -0,0 +1,162 @@
+package poller
+
+/*
+ * 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"
+	"math/rand"
+	"testing"
+	"time"
+)
+
+func TestHeap(t *testing.T) {
+	h := &Heap{}
+
+	num := 100
+	for i := 0; i < num; i++ {
+		h.Push(HeapPollInfo{
+			Info: HTTPPollInfo{
+				Interval: time.Second * time.Duration(8),
+				ID:       fmt.Sprintf("%v", i),
+			},
+			Next: time.Now().Add(time.Second * time.Duration(i)), // time.Duration((i%2)*-1)
+		})
+	}
+
+	for i := 0; i < num; i++ {
+		val, ok := h.Pop()
+		if !ok {
+			t.Errorf("expected pop ID %v got empty heap", i)
+		} else if val.Info.ID != fmt.Sprintf("%v", i) {
+			t.Errorf("expected pop ID %v got %v next %v", i, val.Info.ID, val.Next)
+		}
+	}
+}
+
+func TestHeapRandom(t *testing.T) {
+	h := &Heap{}
+
+	num := 10
+	for i := 0; i < num; i++ {
+		h.Push(HeapPollInfo{
+			Info: HTTPPollInfo{
+				Interval: time.Second * time.Duration(8),
+				ID:       fmt.Sprintf("%v", i),
+			},
+			Next: time.Now().Add(time.Duration(rand.Int63())),
+		})
+	}
+
+	previousTime := time.Now()
+	for i := 0; i < num; i++ {
+		val, ok := h.Pop()
+		if !ok {
+			t.Errorf("expected pop ID %v got empty heap", i)
+		} else if previousTime.After(val.Next) {
+			t.Errorf("heap pop %v < previous %v expected >", val.Next, previousTime)
+		}
+		previousTime = val.Next
+	}
+}
+
+func TestHeapRandomPopping(t *testing.T) {
+	h := &Heap{}
+
+	randInfo := func(id int) HeapPollInfo {
+		return HeapPollInfo{
+			Info: HTTPPollInfo{
+				Interval: time.Second * time.Duration(8),
+				ID:       fmt.Sprintf("%v", id),
+			},
+			Next: time.Now().Add(time.Duration(rand.Int63())),
+		}
+	}
+
+	num := 10
+	for i := 0; i < num; i++ {
+		h.Push(randInfo(i))
+	}
+
+	previousTime := time.Now()
+	for i := 0; i < num/2; i++ {
+		val, ok := h.Pop()
+		if !ok {
+			t.Errorf("expected pop ID %v got empty heap", i)
+		} else if previousTime.After(val.Next) {
+			t.Errorf("heap pop %v < previous %v expected >", val.Next, previousTime)
+		}
+		previousTime = val.Next
+	}
+
+	for i := 0; i < num; i++ {
+		h.Push(randInfo(i))
+	}
+	val, ok := h.Pop()
+	if !ok {
+		t.Errorf("expected pop, got empty heap")
+	} else {
+		previousTime = val.Next
+	}
+
+	for i := 0; i < num; i++ {
+		val, ok := h.Pop()
+		if !ok {
+			t.Errorf("expected pop ID %v got empty heap", i)
+		} else if previousTime.After(val.Next) {
+			t.Errorf("heap pop %v < previous %v expected >", val.Next, previousTime)
+		}
+		previousTime = val.Next
+	}
+
+	for i := 0; i < num; i++ {
+		h.Push(randInfo(i))
+	}
+	val, ok = h.Pop()
+	if !ok {
+		t.Errorf("expected pop, got empty heap")
+	} else {
+		previousTime = val.Next
+	}
+
+	for i := 0; i < num; i++ {
+		val, ok := h.Pop()
+		if !ok {
+			t.Errorf("expected pop ID %v got empty heap", i)
+		} else if previousTime.After(val.Next) {
+			t.Errorf("heap pop %v < previous %v expected >", val.Next, previousTime)
+		}
+		previousTime = val.Next
+	}
+
+	for i := 0; i < num/2-2; i++ { // -2 for the two we manually popped in order to get the max
+		val, ok := h.Pop()
+		if !ok {
+			t.Errorf("expected pop ID %v got empty heap", i)
+		} else if previousTime.After(val.Next) {
+			t.Errorf("heap pop %v < previous %v expected >", val.Next, previousTime)
+		}
+		previousTime = val.Next
+	}
+
+	val, ok = h.Pop()
+	if ok {
+		t.Errorf("expected empty, got %+v", val)
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/common/poller/poller.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/common/poller/poller.go b/traffic_monitor_golang/common/poller/poller.go
new file mode 100644
index 0000000..cae41de
--- /dev/null
+++ b/traffic_monitor_golang/common/poller/poller.go
@@ -0,0 +1,399 @@
+package poller
+
+/*
+ * 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 (
+	"io/ioutil"
+	"math/rand"
+	"net/http"
+	"os"
+	"sync/atomic"
+	"time"
+
+	"gopkg.in/fsnotify.v1"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/fetcher"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/handler"
+	instr "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/instrumentation"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
+	towrap "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopswrapper" // TODO move to common
+	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
+)
+
+type Poller interface {
+	Poll()
+}
+
+type HttpPoller struct {
+	Config          HttpPollerConfig
+	ConfigChannel   chan HttpPollerConfig
+	FetcherTemplate fetcher.HttpFetcher // FetcherTemplate has all the constant settings, and is copied to create fetchers with custom HTTP client timeouts.
+	TickChan        chan uint64
+}
+
+type PollConfig struct {
+	URL     string
+	Timeout time.Duration
+	Handler handler.Handler
+}
+
+type HttpPollerConfig struct {
+	Urls     map[string]PollConfig
+	Interval time.Duration
+	// noSleep indicates to use the InsomniacPoller. Note this is only used with the initial Poll call, which decides which Poller mechanism to use. After that, this is ignored when the HttpPollerConfig is passed over the ConfigChannel.
+	noSleep bool
+}
+
+// NewHTTP creates and returns a new HttpPoller.
+// If tick is false, HttpPoller.TickChan() will return nil. If noSleep is true, the poller will busywait instead of sleeping, and use a single goroutine which dispatches polls instead of a goroutine per poll.
+func NewHTTP(
+	interval time.Duration,
+	tick bool,
+	httpClient *http.Client,
+	counters fetcher.Counters,
+	fetchHandler handler.Handler,
+	noSleep bool,
+) HttpPoller {
+	var tickChan chan uint64
+	if tick {
+		tickChan = make(chan uint64)
+	}
+	return HttpPoller{
+		TickChan:      tickChan,
+		ConfigChannel: make(chan HttpPollerConfig),
+		Config: HttpPollerConfig{
+			Interval: interval,
+			noSleep:  noSleep,
+		},
+		FetcherTemplate: fetcher.HttpFetcher{
+			Handler:  fetchHandler,
+			Client:   httpClient,
+			Counters: counters,
+		},
+	}
+}
+
+type FilePoller struct {
+	File                string
+	ResultChannel       chan interface{}
+	NotificationChannel chan int
+}
+
+type MonitorConfigPoller struct {
+	Session          towrap.ITrafficOpsSession
+	SessionChannel   chan towrap.ITrafficOpsSession
+	ConfigChannel    chan to.TrafficMonitorConfigMap
+	OpsConfigChannel chan handler.OpsConfig
+	Interval         time.Duration
+	OpsConfig        handler.OpsConfig
+}
+
+// Creates and returns a new HttpPoller.
+// If tick is false, HttpPoller.TickChan() will return nil
+func NewMonitorConfig(interval time.Duration) MonitorConfigPoller {
+	return MonitorConfigPoller{
+		Interval:         interval,
+		SessionChannel:   make(chan towrap.ITrafficOpsSession),
+		ConfigChannel:    make(chan to.TrafficMonitorConfigMap),
+		OpsConfigChannel: make(chan handler.OpsConfig),
+	}
+}
+
+func (p MonitorConfigPoller) Poll() {
+	tick := time.NewTicker(p.Interval)
+	defer tick.Stop()
+	for {
+		select {
+		case opsConfig := <-p.OpsConfigChannel:
+			log.Infof("MonitorConfigPoller: received new opsConfig: %v\n", opsConfig)
+			p.OpsConfig = opsConfig
+		case session := <-p.SessionChannel:
+			log.Infof("MonitorConfigPoller: received new session: %v\n", session)
+			p.Session = session
+		case <-tick.C:
+			if p.Session != nil && p.OpsConfig.CdnName != "" {
+				monitorConfig, err := p.Session.TrafficMonitorConfigMap(p.OpsConfig.CdnName)
+
+				if err != nil {
+					log.Errorf("MonitorConfigPoller: %s\n %v\n", err, monitorConfig)
+				} else {
+					log.Debugln("MonitorConfigPoller: fetched monitorConfig")
+					p.ConfigChannel <- *monitorConfig
+				}
+			} else {
+				log.Warnln("MonitorConfigPoller: skipping this iteration, Session is nil")
+			}
+		}
+	}
+}
+
+var debugPollNum uint64
+
+type HTTPPollInfo struct {
+	Interval time.Duration
+	Timeout  time.Duration
+	ID       string
+	URL      string
+	Handler  handler.Handler
+}
+
+func (p HttpPoller) Poll() {
+	if p.Config.noSleep {
+		log.Debugf("HttpPoller using InsomniacPoll\n")
+		p.InsomniacPoll()
+	} else {
+		log.Debugf("HttpPoller using SleepPoll\n")
+		p.SleepPoll()
+	}
+}
+
+func (p HttpPoller) SleepPoll() {
+	// iterationCount := uint64(0)
+	// iterationCount++ // on tick<:
+	// case p.TickChan <- iterationCount:
+	killChans := map[string]chan<- struct{}{}
+	for newConfig := range p.ConfigChannel {
+		deletions, additions := diffConfigs(p.Config, newConfig)
+		for _, id := range deletions {
+			killChan := killChans[id]
+			go func() { killChan <- struct{}{} }() // go - we don't want to wait for old polls to die.
+			delete(killChans, id)
+		}
+		for _, info := range additions {
+			kill := make(chan struct{})
+			killChans[info.ID] = kill
+
+			fetcher := p.FetcherTemplate
+			if info.Timeout != 0 { // if the timeout isn't explicitly set, use the template value.
+				c := *fetcher.Client
+				fetcher.Client = &c // copy the client, so we don't change other fetchers.
+				fetcher.Client.Timeout = info.Timeout
+			}
+			go sleepPoller(info.Interval, info.ID, info.URL, fetcher, kill)
+		}
+		p.Config = newConfig
+	}
+}
+
+func mustDie(die <-chan struct{}) bool {
+	select {
+	case <-die:
+		return true
+	default:
+	}
+	return false
+}
+
+// TODO iterationCount and/or p.TickChan?
+func sleepPoller(interval time.Duration, id string, url string, fetcher fetcher.Fetcher, die <-chan struct{}) {
+	pollSpread := time.Duration(rand.Float64()*float64(interval/time.Nanosecond)) * time.Nanosecond
+	time.Sleep(pollSpread)
+	tick := time.NewTicker(interval)
+	lastTime := time.Now()
+	for {
+		select {
+		case <-tick.C:
+			realInterval := time.Now().Sub(lastTime)
+			if realInterval > interval+(time.Millisecond*100) {
+				instr.TimerFail.Inc()
+				log.Debugf("Intended Duration: %v Actual Duration: %v\n", interval, realInterval)
+			}
+			lastTime = time.Now()
+
+			pollId := atomic.AddUint64(&debugPollNum, 1)
+			pollFinishedChan := make(chan uint64)
+			log.Debugf("poll %v %v start\n", pollId, time.Now())
+			go fetcher.Fetch(id, url, pollId, pollFinishedChan) // TODO persist fetcher, with its own die chan?
+			<-pollFinishedChan
+		case <-die:
+			tick.Stop()
+			return
+		}
+	}
+}
+
+const InsomniacPollerEmptySleepDuration = time.Millisecond * time.Duration(100)
+
+// InsomniacPoll polls using a single thread, which never sleeps. This exists to work around a bug observed in OpenStack CentOS 6.5 kernel 2.6.32 wherin sleep gets progressively slower. This should be removed and Poll() changed to call SleepPoll() when the bug is tracked down and fixed for production.
+func (p HttpPoller) InsomniacPoll() {
+	// iterationCount := uint64(0)
+	// iterationCount++ // on tick<:
+	// case p.TickChan <- iterationCount:
+	killChan := make(chan struct{})
+	pollRunning := false // TODO find less awkward way to not kill the first loop
+	pollerId := rand.Int63()
+	for newCfg := range p.ConfigChannel {
+		// TODO add a more efficient function than diffConfigs for this func, since we only need to know whether anything changed
+		deletions, additions := diffConfigs(p.Config, newCfg)
+		if len(deletions) == 0 && len(additions) == 0 {
+			continue
+		}
+
+		if pollRunning {
+			killChan <- struct{}{}
+		}
+		pollRunning = true
+
+		polls := []HTTPPollInfo{}
+		for id, pollCfg := range newCfg.Urls {
+			polls = append(polls, HTTPPollInfo{
+				Interval: newCfg.Interval - InsomniacPollerEmptySleepDuration,
+				ID:       id,
+				URL:      pollCfg.URL,
+				Timeout:  pollCfg.Timeout,
+			})
+		}
+		go insomniacPoller(pollerId, polls, p.FetcherTemplate, killChan)
+		p.Config = newCfg
+	}
+}
+
+func insomniacPoller(pollerId int64, polls []HTTPPollInfo, fetcherTemplate fetcher.HttpFetcher, die <-chan struct{}) {
+	heap := Heap{PollerID: pollerId}
+	start := time.Now()
+	fetchers := map[string]fetcher.Fetcher{}
+	for _, p := range polls {
+		spread := time.Duration(rand.Float64()*float64(p.Interval/time.Nanosecond)) * time.Nanosecond
+		heap.Push(HeapPollInfo{Info: p, Next: start.Add(spread)})
+
+		fetcher := fetcherTemplate
+		if p.Timeout != 0 { // if the timeout isn't explicitly set, use the template value.
+			c := *fetcher.Client
+			fetcher.Client = &c // copy the client, so we don't change other fetchers.
+			fetcher.Client.Timeout = p.Timeout
+		}
+		fetchers[p.ID] = fetcher
+	}
+
+	timeMax := func(a time.Time, b time.Time) time.Time {
+		if a.After(b) {
+			return a
+		}
+		return b
+	}
+
+	poll := func(p HeapPollInfo) {
+		start := time.Now()
+		pollId := atomic.AddUint64(&debugPollNum, 1)
+		// TODO change pollFinishedChan to callback, for performance
+		pollFinishedChan := make(chan uint64)
+
+		go fetchers[p.Info.ID].Fetch(p.Info.ID, p.Info.URL, pollId, pollFinishedChan) // TODO persist fetcher, with its own die chan?
+		<-pollFinishedChan
+		now := time.Now()
+		p.Next = timeMax(start.Add(p.Info.Interval), now)
+		heap.Push(p)
+	}
+
+	for {
+		p, ok := heap.Pop()
+		if !ok {
+			ThreadSleep(InsomniacPollerEmptySleepDuration)
+			continue
+		}
+		if mustDie(die) {
+			return
+		}
+		ThreadSleep(p.Next.Sub(time.Now()))
+		go poll(p)
+	}
+}
+
+func (p FilePoller) Poll() {
+	// initial read before watching for changes
+	contents, err := ioutil.ReadFile(p.File)
+
+	if err != nil {
+		log.Errorf("reading %s: %s\n", p.File, err)
+		os.Exit(1) // TODO: this is a little drastic -jse
+	} else {
+		p.ResultChannel <- contents
+	}
+
+	watcher, _ := fsnotify.NewWatcher()
+	watcher.Add(p.File)
+
+	for {
+		select {
+		case event := <-watcher.Events:
+			if event.Op&fsnotify.Write == fsnotify.Write {
+				contents, err := ioutil.ReadFile(p.File)
+
+				if err != nil {
+					log.Errorf("opening %s: %s\n", p.File, err)
+				} else {
+					p.ResultChannel <- contents
+				}
+			}
+		case err := <-watcher.Errors:
+			log.Errorln(time.Now(), "error:", err)
+		}
+	}
+}
+
+// diffConfigs takes the old and new configs, and returns a list of deleted IDs, and a list of new polls to do
+func diffConfigs(old HttpPollerConfig, new HttpPollerConfig) ([]string, []HTTPPollInfo) {
+	deletions := []string{}
+	additions := []HTTPPollInfo{}
+
+	if old.Interval != new.Interval {
+		for id, _ := range old.Urls {
+			deletions = append(deletions, id)
+		}
+		for id, pollCfg := range new.Urls {
+			additions = append(additions, HTTPPollInfo{
+				Interval: new.Interval,
+				ID:       id,
+				URL:      pollCfg.URL,
+				Timeout:  pollCfg.Timeout,
+			})
+		}
+		return deletions, additions
+	}
+
+	for id, oldPollCfg := range old.Urls {
+		newPollCfg, newIdExists := new.Urls[id]
+		if !newIdExists {
+			deletions = append(deletions, id)
+		} else if newPollCfg != oldPollCfg {
+			deletions = append(deletions, id)
+			additions = append(additions, HTTPPollInfo{
+				Interval: new.Interval,
+				ID:       id,
+				URL:      newPollCfg.URL,
+				Timeout:  newPollCfg.Timeout,
+			})
+		}
+	}
+
+	for id, newPollCfg := range new.Urls {
+		_, oldIdExists := old.Urls[id]
+		if !oldIdExists {
+			additions = append(additions, HTTPPollInfo{
+				Interval: new.Interval,
+				ID:       id,
+				URL:      newPollCfg.URL,
+				Timeout:  newPollCfg.Timeout,
+			})
+		}
+	}
+
+	return deletions, additions
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/common/poller/threadsleep.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/common/poller/threadsleep.go b/traffic_monitor_golang/common/poller/threadsleep.go
new file mode 100644
index 0000000..d4a67cc
--- /dev/null
+++ b/traffic_monitor_golang/common/poller/threadsleep.go
@@ -0,0 +1,36 @@
+// +build !linux
+
+/*
+ * 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.
+ */
+
+package poller
+
+import (
+	"runtime"
+	"time"
+)
+
+// ThreadSleep actually busywaits for the given duration. This is becuase Go doesn't have Mac and Windows nanosleep syscalls, and `Sleep` sleeps for progressively longer than requested.
+func ThreadSleep(d time.Duration) {
+	// TODO fix to not busywait on Mac, Windows. We can't simply Sleep, because Sleep gets progressively slower as the app runs, due to a Go runtime issue. If this is changed, you MUST verify the poll doesn't get slower after the app runs for several days.
+	end := time.Now().Add(d)
+	for end.After(time.Now()) {
+		runtime.Gosched()
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/common/poller/threadsleep_linux.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/common/poller/threadsleep_linux.go b/traffic_monitor_golang/common/poller/threadsleep_linux.go
new file mode 100644
index 0000000..ba7c379
--- /dev/null
+++ b/traffic_monitor_golang/common/poller/threadsleep_linux.go
@@ -0,0 +1,42 @@
+// +build linux
+
+/*
+ * 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.
+ */
+
+package poller
+
+import (
+	"errors"
+	"golang.org/x/sys/unix"
+	"time"
+)
+
+// ThreadSleep sleeps using the POSIX syscall `nanosleep`. Note this does not sleep the goroutine, but the operating system thread itself. This should only be called by a goroutine which has previously called `LockOSThread`. This exists due to a bug with `time.Sleep` getting progressively slower as the app runs, and should be removed if the bug in Go is fixed.
+func ThreadSleep(d time.Duration) {
+	if d < 0 {
+		d = 0
+	}
+	t := unix.Timespec{}
+	leftover := unix.NsecToTimespec(d.Nanoseconds())
+	err := errors.New("")
+	for err != nil && (leftover.Sec != 0 || leftover.Nsec != 0) {
+		t = leftover
+		err = unix.Nanosleep(&t, &leftover)
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/common/util/join.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/common/util/join.go b/traffic_monitor_golang/common/util/join.go
new file mode 100644
index 0000000..d5b647b
--- /dev/null
+++ b/traffic_monitor_golang/common/util/join.go
@@ -0,0 +1,60 @@
+package util
+
+/*
+ * 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"
+)
+
+func JoinErrorsString(errs []error) string {
+	joined := JoinErrors(errs)
+
+	if joined == nil {
+		return ""
+	}
+
+	return joined.Error()
+}
+
+func JoinErrors(errs []error) error {
+	return JoinErrorsSep(errs, "")
+}
+
+func JoinErrorsSep(errs []error, separator string) error {
+	if separator == "" {
+		separator = ", "
+	}
+
+	joinedErrors := ""
+
+	for _, err := range errs {
+		if err != nil {
+			joinedErrors += err.Error() + separator
+		}
+	}
+
+	if len(joinedErrors) == 0 {
+		return nil
+	}
+
+	joinedErrors = joinedErrors[:len(joinedErrors)-len(separator)] // strip trailing separator
+
+	return fmt.Errorf("%s", joinedErrors)
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/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
new file mode 100644
index 0000000..4db2cf9
--- /dev/null
+++ b/traffic_monitor_golang/common/util/num.go
@@ -0,0 +1,35 @@
+package util
+
+// 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?
+func ToNumeric(v interface{}) (float64, bool) {
+	switch i := v.(type) {
+	case uint8:
+		return float64(i), true
+	case uint16:
+		return float64(i), true
+	case uint32:
+		return float64(i), true
+	case uint64:
+		return float64(i), true
+	case int8:
+		return float64(i), true
+	case int16:
+		return float64(i), true
+	case int32:
+		return float64(i), true
+	case int64:
+		return float64(i), true
+	case float32:
+		return float64(i), true
+	case float64:
+		return i, true
+	case int:
+		return float64(i), true
+	case uint:
+		return float64(i), true
+	default:
+		return 0.0, false
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/conf/traffic_monitor.cfg
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/conf/traffic_monitor.cfg b/traffic_monitor_golang/conf/traffic_monitor.cfg
new file mode 100644
index 0000000..f6698d1
--- /dev/null
+++ b/traffic_monitor_golang/conf/traffic_monitor.cfg
@@ -0,0 +1,22 @@
+{
+	"cache_health_polling_interval_ms": 6000,
+	"cache_stat_polling_interval_ms": 6000,
+	"monitor_config_polling_interval_ms": 5000,
+	"http_timeout_ms": 2000,
+	"peer_polling_interval_ms": 5000,
+	"peer_optimistic": true,
+	"max_events": 200,
+	"max_stat_history": 5,
+	"max_health_history": 5,
+	"health_flush_interval_ms": 20,
+	"stat_flush_interval_ms": 20,
+	"log_location_event": "null",
+	"log_location_error": "stderr",
+	"log_location_warning": "stdout",
+	"log_location_info": "null",
+	"log_location_debug": "null",
+	"serve_read_timeout_ms": 10000,
+	"serve_write_timeout_ms": 10000,
+	"http_poll_no_sleep": false,
+	"static_file_dir": "/opt/traffic_monitor/static/"
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/conf/traffic_ops.cfg
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/conf/traffic_ops.cfg b/traffic_monitor_golang/conf/traffic_ops.cfg
new file mode 100644
index 0000000..b5fcf83
--- /dev/null
+++ b/traffic_monitor_golang/conf/traffic_ops.cfg
@@ -0,0 +1,8 @@
+{
+	"username": "",
+	"password": "",
+	"url": "https://traffic-ops.example.net",
+	"insecure": true,
+	"cdnName": "cdn",
+	"httpListener": ":8080"
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/README.md
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/README.md b/traffic_monitor_golang/traffic_monitor/README.md
new file mode 100644
index 0000000..c3534fb
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/README.md
@@ -0,0 +1 @@
+To run: `./build.sh && traffic_monitor --opsCfg ./traffic_ops.cfg -config ./traffic_monitor-example-config.json`

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/build.sh
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/build.sh b/traffic_monitor_golang/traffic_monitor/build.sh
new file mode 100755
index 0000000..baf07cc
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/build.sh
@@ -0,0 +1,18 @@
+# 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.
+#!/usr/bin/env bash
+go build -ldflags "-X main.GitRevision=`git rev-parse HEAD` -X main.BuildTimestamp=`date +'%Y-%M-%dT%H:%M:%S'`"

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/cache/astats.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/cache/astats.go b/traffic_monitor_golang/traffic_monitor/cache/astats.go
new file mode 100644
index 0000000..96258c8
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/cache/astats.go
@@ -0,0 +1,51 @@
+package cache
+
+/*
+ * 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"
+)
+
+// Astats contains ATS data returned from the Astats ATS plugin. This includes generic stats, as well as fixed system stats.
+type Astats struct {
+	Ats    map[string]interface{} `json:"ats"`
+	System AstatsSystem           `json:"system"`
+}
+
+// AstatsSystem represents fixed system stats returne from ATS by the Astats plugin.
+type AstatsSystem struct {
+	InfName           string `json:"inf.name"`
+	InfSpeed          int    `json:"inf.speed"`
+	ProcNetDev        string `json:"proc.net.dev"`
+	ProcLoadavg       string `json:"proc.loadavg"`
+	ConfigLoadRequest int    `json:"configReloadRequests"`
+	LastReloadRequest int    `json:"lastReloadRequest"`
+	ConfigReloads     int    `json:"configReloads"`
+	LastReload        int    `json:"lastReload"`
+	AstatsLoad        int    `json:"astatsLoad"`
+}
+
+// Unmarshal unmarshalls the given bytes, which must be JSON Astats data, into an Astats object.
+func Unmarshal(body []byte) (Astats, error) {
+	var aStats Astats
+	err := json.Unmarshal(body, &aStats)
+	return aStats, err
+}


[17/19] incubator-trafficcontrol git commit: Move TM2 to trafficcontrol/traffic_monitor_golang

Posted by ne...@apache.org.
Move TM2 to trafficcontrol/traffic_monitor_golang


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

Branch: refs/heads/master
Commit: 594b85177e68efa5a620e87d2475ecdc86482e68
Parents: 43cfb5a
Author: Robert Butts <ro...@gmail.com>
Authored: Thu Jan 26 14:31:39 2017 -0700
Committer: Dave Neuman <ne...@apache.org>
Committed: Mon Jan 30 08:28:46 2017 -0700

----------------------------------------------------------------------
 traffic_monitor/experimental/build/build_rpm.sh |   59 -
 .../experimental/build/traffic_monitor.init     |  119 --
 .../build/traffic_monitor.logrotate             |   26 -
 .../experimental/build/traffic_monitor.spec     |  155 ---
 .../experimental/common/adapter/adapter.go      |   29 -
 .../experimental/common/crstates/crstates.go    |   37 -
 .../experimental/common/fetcher/fetcher.go      |  100 --
 .../experimental/common/handler/handler.go      |   68 -
 .../common/instrumentation/instrumentation.go   |   31 -
 traffic_monitor/experimental/common/log/log.go  |  112 --
 .../experimental/common/poller/heap.go          |  106 --
 .../experimental/common/poller/heap_test.go     |  162 ---
 .../experimental/common/poller/poller.go        |  399 ------
 .../experimental/common/poller/threadsleep.go   |   36 -
 .../common/poller/threadsleep_linux.go          |   42 -
 .../experimental/common/util/join.go            |   60 -
 traffic_monitor/experimental/common/util/num.go |   35 -
 .../experimental/conf/traffic_monitor.cfg       |   22 -
 .../experimental/conf/traffic_ops.cfg           |    8 -
 .../experimental/traffic_monitor/README.md      |    1 -
 .../experimental/traffic_monitor/build.sh       |   18 -
 .../traffic_monitor/cache/astats.go             |   51 -
 .../traffic_monitor/cache/astats.json           |  531 --------
 .../traffic_monitor/cache/astats_test.go        |   42 -
 .../experimental/traffic_monitor/cache/cache.go |  559 --------
 .../traffic_monitor/cache/cache_test.go         |   91 --
 .../experimental/traffic_monitor/cache/data.go  |  219 ----
 .../traffic_monitor/cache/data_test.go          |  256 ----
 .../traffic_monitor/config/config.go            |  186 ---
 .../traffic_monitor/deliveryservice/stat.go     |  358 -----
 .../traffic_monitor/deliveryservicedata/stat.go |  488 -------
 .../experimental/traffic_monitor/enum/enum.go   |  181 ---
 .../traffic_monitor/health/cache.go             |  272 ----
 .../traffic_monitor/health/event.go             |   83 --
 .../traffic_monitor/manager/datarequest.go      | 1221 -----------------
 .../traffic_monitor/manager/health.go           |  245 ----
 .../traffic_monitor/manager/manager.go          |  176 ---
 .../traffic_monitor/manager/monitorconfig.go    |  282 ----
 .../traffic_monitor/manager/opsconfig.go        |  186 ---
 .../traffic_monitor/manager/peer.go             |  165 ---
 .../traffic_monitor/manager/stat.go             |  219 ----
 .../traffic_monitor/peer/crstates.go            |  233 ----
 .../traffic_monitor/peer/crstates.json          |   16 -
 .../experimental/traffic_monitor/peer/peer.go   |   79 --
 .../traffic_monitor/peer/peer_test.go           |   50 -
 .../traffic_monitor/srvhttp/srvhttp.go          |  164 ---
 .../traffic_monitor/static/index.html           |  560 --------
 .../traffic_monitor/static/sorttable.js         |  495 -------
 .../threadsafe/cacheavailablestatus.go          |   52 -
 .../threadsafe/cachemaxkbpses.go                |   52 -
 .../traffic_monitor/threadsafe/dsstats.go       |   57 -
 .../traffic_monitor/threadsafe/lastkbpsstats.go |   52 -
 .../traffic_monitor/threadsafe/polledcaches.go  |  147 ---
 .../traffic_monitor/threadsafe/resulthistory.go |   54 -
 .../threadsafe/resultstathistory.go             |   81 --
 .../traffic_monitor/threadsafe/uint.go          |   51 -
 .../traffic_monitor/traffic_monitor.go          |  157 ---
 .../trafficopsdata/trafficopsdata.go            |  290 ----
 .../trafficopswrapper/trafficopswrapper.go      |  128 --
 .../experimental/traffic_monitor/version.go     |   23 -
 .../vendor/github.com/davecheney/gmx/.gitignore |    1 -
 .../vendor/github.com/davecheney/gmx/LICENCE    |    8 -
 .../vendor/github.com/davecheney/gmx/README.md  |   52 -
 .../davecheney/gmx/example/helloworld/README.md |   19 -
 .../davecheney/gmx/example/helloworld/main.go   |   14 -
 .../vendor/github.com/davecheney/gmx/gmx.go     |  129 --
 .../github.com/davecheney/gmx/gmxc/README.md    |   27 -
 .../github.com/davecheney/gmx/gmxc/main.go      |  129 --
 .../github.com/davecheney/gmx/instrument.go     |   41 -
 .../vendor/github.com/davecheney/gmx/os.go      |   15 -
 .../vendor/github.com/davecheney/gmx/runtime.go |   42 -
 .../hydrogen18/stoppableListener/LICENSE        |   10 -
 .../hydrogen18/stoppableListener/README.md      |   18 -
 .../stoppableListener/example/example.go        |   52 -
 .../hydrogen18/stoppableListener/listener.go    |   62 -
 .../vendor/gopkg.in/fsnotify.v1/.gitignore      |    6 -
 .../vendor/gopkg.in/fsnotify.v1/.travis.yml     |   29 -
 .../vendor/gopkg.in/fsnotify.v1/AUTHORS         |   43 -
 .../vendor/gopkg.in/fsnotify.v1/CHANGELOG.md    |  291 ----
 .../vendor/gopkg.in/fsnotify.v1/CONTRIBUTING.md |   77 --
 .../vendor/gopkg.in/fsnotify.v1/LICENSE         |   28 -
 .../vendor/gopkg.in/fsnotify.v1/README.md       |   50 -
 .../vendor/gopkg.in/fsnotify.v1/example_test.go |   42 -
 .../vendor/gopkg.in/fsnotify.v1/fen.go          |   37 -
 .../vendor/gopkg.in/fsnotify.v1/fsnotify.go     |   62 -
 .../vendor/gopkg.in/fsnotify.v1/inotify.go      |  325 -----
 .../gopkg.in/fsnotify.v1/inotify_poller.go      |  187 ---
 .../gopkg.in/fsnotify.v1/inotify_poller_test.go |  229 ----
 .../vendor/gopkg.in/fsnotify.v1/inotify_test.go |  344 -----
 .../fsnotify.v1/integration_darwin_test.go      |  147 ---
 .../gopkg.in/fsnotify.v1/integration_test.go    | 1237 ------------------
 .../vendor/gopkg.in/fsnotify.v1/kqueue.go       |  503 -------
 .../gopkg.in/fsnotify.v1/open_mode_bsd.go       |   11 -
 .../gopkg.in/fsnotify.v1/open_mode_darwin.go    |   12 -
 .../vendor/gopkg.in/fsnotify.v1/windows.go      |  561 --------
 traffic_monitor_golang/build/build_rpm.sh       |   58 +
 .../build/traffic_monitor.init                  |  119 ++
 .../build/traffic_monitor.logrotate             |   26 +
 .../build/traffic_monitor.spec                  |  155 +++
 .../common/adapter/adapter.go                   |   29 +
 .../common/crstates/crstates.go                 |   37 +
 .../common/fetcher/fetcher.go                   |  100 ++
 .../common/handler/handler.go                   |   68 +
 .../common/instrumentation/instrumentation.go   |   31 +
 traffic_monitor_golang/common/log/log.go        |  112 ++
 traffic_monitor_golang/common/poller/heap.go    |  106 ++
 .../common/poller/heap_test.go                  |  162 +++
 traffic_monitor_golang/common/poller/poller.go  |  399 ++++++
 .../common/poller/threadsleep.go                |   36 +
 .../common/poller/threadsleep_linux.go          |   42 +
 traffic_monitor_golang/common/util/join.go      |   60 +
 traffic_monitor_golang/common/util/num.go       |   35 +
 traffic_monitor_golang/conf/traffic_monitor.cfg |   22 +
 traffic_monitor_golang/conf/traffic_ops.cfg     |    8 +
 .../traffic_monitor/README.md                   |    1 +
 traffic_monitor_golang/traffic_monitor/build.sh |   18 +
 .../traffic_monitor/cache/astats.go             |   51 +
 .../traffic_monitor/cache/astats.json           |  531 ++++++++
 .../traffic_monitor/cache/astats_test.go        |   42 +
 .../traffic_monitor/cache/cache.go              |  559 ++++++++
 .../traffic_monitor/cache/cache_test.go         |   91 ++
 .../traffic_monitor/cache/data.go               |  219 ++++
 .../traffic_monitor/cache/data_test.go          |  256 ++++
 .../traffic_monitor/config/config.go            |  186 +++
 .../traffic_monitor/deliveryservice/stat.go     |  358 +++++
 .../traffic_monitor/deliveryservicedata/stat.go |  488 +++++++
 .../traffic_monitor/enum/enum.go                |  181 +++
 .../traffic_monitor/health/cache.go             |  272 ++++
 .../traffic_monitor/health/event.go             |   83 ++
 .../traffic_monitor/manager/datarequest.go      | 1221 +++++++++++++++++
 .../traffic_monitor/manager/health.go           |  245 ++++
 .../traffic_monitor/manager/manager.go          |  176 +++
 .../traffic_monitor/manager/monitorconfig.go    |  282 ++++
 .../traffic_monitor/manager/opsconfig.go        |  186 +++
 .../traffic_monitor/manager/peer.go             |  165 +++
 .../traffic_monitor/manager/stat.go             |  219 ++++
 .../traffic_monitor/peer/crstates.go            |  233 ++++
 .../traffic_monitor/peer/crstates.json          |   16 +
 .../traffic_monitor/peer/peer.go                |   79 ++
 .../traffic_monitor/peer/peer_test.go           |   50 +
 .../traffic_monitor/srvhttp/srvhttp.go          |  164 +++
 .../traffic_monitor/static/index.html           |  560 ++++++++
 .../traffic_monitor/static/sorttable.js         |  495 +++++++
 .../threadsafe/cacheavailablestatus.go          |   52 +
 .../threadsafe/cachemaxkbpses.go                |   52 +
 .../traffic_monitor/threadsafe/dsstats.go       |   57 +
 .../traffic_monitor/threadsafe/lastkbpsstats.go |   52 +
 .../traffic_monitor/threadsafe/polledcaches.go  |  147 +++
 .../traffic_monitor/threadsafe/resulthistory.go |   54 +
 .../threadsafe/resultstathistory.go             |   81 ++
 .../traffic_monitor/threadsafe/uint.go          |   51 +
 .../traffic_monitor/traffic_monitor.go          |  157 +++
 .../trafficopsdata/trafficopsdata.go            |  290 ++++
 .../trafficopswrapper/trafficopswrapper.go      |  128 ++
 .../traffic_monitor/version.go                  |   23 +
 .../vendor/github.com/davecheney/gmx/.gitignore |    1 +
 .../vendor/github.com/davecheney/gmx/LICENCE    |    8 +
 .../vendor/github.com/davecheney/gmx/README.md  |   52 +
 .../davecheney/gmx/example/helloworld/README.md |   19 +
 .../davecheney/gmx/example/helloworld/main.go   |   14 +
 .../vendor/github.com/davecheney/gmx/gmx.go     |  129 ++
 .../github.com/davecheney/gmx/gmxc/README.md    |   27 +
 .../github.com/davecheney/gmx/gmxc/main.go      |  129 ++
 .../github.com/davecheney/gmx/instrument.go     |   41 +
 .../vendor/github.com/davecheney/gmx/os.go      |   15 +
 .../vendor/github.com/davecheney/gmx/runtime.go |   42 +
 .../hydrogen18/stoppableListener/LICENSE        |   10 +
 .../hydrogen18/stoppableListener/README.md      |   18 +
 .../stoppableListener/example/example.go        |   52 +
 .../hydrogen18/stoppableListener/listener.go    |   62 +
 .../vendor/gopkg.in/fsnotify.v1/.gitignore      |    6 +
 .../vendor/gopkg.in/fsnotify.v1/.travis.yml     |   29 +
 .../vendor/gopkg.in/fsnotify.v1/AUTHORS         |   43 +
 .../vendor/gopkg.in/fsnotify.v1/CHANGELOG.md    |  291 ++++
 .../vendor/gopkg.in/fsnotify.v1/CONTRIBUTING.md |   77 ++
 .../vendor/gopkg.in/fsnotify.v1/LICENSE         |   28 +
 .../vendor/gopkg.in/fsnotify.v1/README.md       |   50 +
 .../vendor/gopkg.in/fsnotify.v1/example_test.go |   42 +
 .../vendor/gopkg.in/fsnotify.v1/fen.go          |   37 +
 .../vendor/gopkg.in/fsnotify.v1/fsnotify.go     |   62 +
 .../vendor/gopkg.in/fsnotify.v1/inotify.go      |  325 +++++
 .../gopkg.in/fsnotify.v1/inotify_poller.go      |  187 +++
 .../gopkg.in/fsnotify.v1/inotify_poller_test.go |  229 ++++
 .../vendor/gopkg.in/fsnotify.v1/inotify_test.go |  344 +++++
 .../fsnotify.v1/integration_darwin_test.go      |  147 +++
 .../gopkg.in/fsnotify.v1/integration_test.go    | 1237 ++++++++++++++++++
 .../vendor/gopkg.in/fsnotify.v1/kqueue.go       |  503 +++++++
 .../gopkg.in/fsnotify.v1/open_mode_bsd.go       |   11 +
 .../gopkg.in/fsnotify.v1/open_mode_darwin.go    |   12 +
 .../vendor/gopkg.in/fsnotify.v1/windows.go      |  561 ++++++++
 190 files changed, 15016 insertions(+), 15017 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/build/build_rpm.sh
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/build/build_rpm.sh b/traffic_monitor/experimental/build/build_rpm.sh
deleted file mode 100755
index 12115a8..0000000
--- a/traffic_monitor/experimental/build/build_rpm.sh
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/bin/bash
-
-#
-#
-# Licensed 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.
-#
-
-function importFunctions() {
-	local script=$(readlink -f "$0")
-	local scriptdir=$(dirname "$script")
-	export goTM_DIR=$(dirname "$scriptdir")
-	export TM_DIR=$(dirname "$goTM_DIR")
-	export TC_DIR=$(dirname "$TM_DIR")
-	functions_sh="$TC_DIR/build/functions.sh"
-	if [[ ! -r $functions_sh ]]; then
-		echo "error: can't find $functions_sh"
-		exit 1
-	fi
-	. "$functions_sh"
-}
-
-#----------------------------------------
-function initBuildArea() {
-	echo "Initializing the build area."
-	mkdir -p "$RPMBUILD"/{SPECS,SOURCES,RPMS,SRPMS,BUILD,BUILDROOT} || { echo "Could not create $RPMBUILD: $?"; exit 1; }
-
-	# tar/gzip the source
-	local tm_dest=$(createSourceDir traffic_monitor)
-	cd "$goTM_DIR" || \
-		 { echo "Could not cd to $goTM_DIR: $?"; exit 1; }
-	rsync -av ./ "$tm_dest"/ || \
-		 { echo "Could not copy to $tm_dest: $?"; exit 1; }
-	cp "$goTM_DIR"/build/*.spec "$RPMBUILD"/SPECS/. || \
-		 { echo "Could not copy spec files: $?"; exit 1; }
-
-	cp -r "$goTM_DIR"/ "$tm_dest" || { echo "Could not copy $goTM_DIR to $tm_dest: $?"; exit 1; }
-
-	tar -czvf "$tm_dest".tgz -C "$RPMBUILD"/SOURCES $(basename $tm_dest) || { echo "Could not create tar archive $tm_dest.tgz: $?"; exit 1; }
-	cp "$goTM_DIR"/build/*.spec "$RPMBUILD"/SPECS/. || { echo "Could not copy spec files: $?"; exit 1; }
-
-	echo "The build area has been initialized."
-}
-
-# ---------------------------------------
-
-importFunctions
-checkEnvironment go
-initBuildArea
-buildRpm traffic_monitor

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/build/traffic_monitor.init
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/build/traffic_monitor.init b/traffic_monitor/experimental/build/traffic_monitor.init
deleted file mode 100644
index 9240ead..0000000
--- a/traffic_monitor/experimental/build/traffic_monitor.init
+++ /dev/null
@@ -1,119 +0,0 @@
-#!/bin/bash
-
-# 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.
-
-# Startup script for traffic_monitor
-#
-#
-# chkconfig: 345 99 10
-# description: traffic_monitor control script
-# processname: traffic_monitor
-
-### BEGIN INIT INFO
-# Provides: traffic_monitor
-# Required-Start: $network $local_fs $syslog
-# Required-Stop: $network $local_fs $syslog
-# Default-Start: 3 4 5
-# Default-Stop: 0 1 2 6
-# Short-Description: start and stop Traffic Monitor
-# Description: Controls all traffic monitor processes at once.
-### END INIT INFO
-
-# Source function library.
-. /etc/init.d/functions
-
-# Source networking configuration.
-. /etc/sysconfig/network
-
-basepath=/opt/traffic_monitor
-binpath=bin
-runpath=var/run
-name=traffic_monitor
-prog=$basepath/$binpath/$name
-lockfile=$basepath/$runpath/$name
-options="--opsCfg /opt/traffic_monitor/conf/traffic_ops.cfg --config /opt/traffic_monitor/conf/traffic_monitor.cfg"
-
-start() {
-        [ "$NETWORKING" = "no" ] && exit 1
-        [ -x $prog ] || exit 5
-
-        #Set file limits
-        # Max open files
-        OPEN_FILE_LIMIT=65536
-        ulimit -n $OPEN_FILE_LIMIT
-        if [ $? -ne 0 ]; then
-            echo -n "Failed to set open file limit to $OPEN_FILE_LIMIT"
-            exit 1
-        fi
-
-        # Start daemons.
-        echo -n $"Starting $name: "
-        daemon nohup $prog $options < /dev/null > /opt/traffic_monitor/var/log/traffic_monitor.log 2>&1 &
-        RETVAL=$?
-        echo
-        [ $RETVAL -eq 0 ] && touch $lockfile
-        return $RETVAL
-}
-
-stop() {
-        echo -n $"Shutting down $name: "
-        killproc $prog
-        RETVAL=$?
-        echo
-        [ $RETVAL -eq 0 ] && rm -f $lockfile
-        return $RETVAL
-}
-
-reload() {
-        echo -n $"Reloading $name: "
-        if [ -n "`pidofproc $prog`" ]; then
-                killproc $prog -HUP
-        else
-                failure $"Reloading $name"
-        fi
-        RETVAL=$?
-        echo
-}
-
-case "$1" in
-  start)
-        start
-        ;;
-  stop)
-        stop
-        ;;
-  status)
-        status $prog
-        ;;
-  restart|force-reload)
-        stop
-        start
-        ;;
-  try-restart|condrestart)
-        if status $prog > /dev/null; then
-            stop
-            start
-        fi
-        ;;
-  reload)
-        reload
-        ;;
-  *)
-        echo $"Usage: $0 {start|stop|status|restart|try-restart|reload|force-reload}"
-        exit 2
-esac

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/build/traffic_monitor.logrotate
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/build/traffic_monitor.logrotate b/traffic_monitor/experimental/build/traffic_monitor.logrotate
deleted file mode 100644
index e7073ad..0000000
--- a/traffic_monitor/experimental/build/traffic_monitor.logrotate
+++ /dev/null
@@ -1,26 +0,0 @@
-# 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.
-
-/opt/traffic_monitor/var/log/traffic_monitor.log {
-        compress
-        maxage 30
-        missingok
-        nomail
-        size 10M
-        rotate 5
-        copytruncate
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/build/traffic_monitor.spec
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/build/traffic_monitor.spec b/traffic_monitor/experimental/build/traffic_monitor.spec
deleted file mode 100644
index 55f02e4..0000000
--- a/traffic_monitor/experimental/build/traffic_monitor.spec
+++ /dev/null
@@ -1,155 +0,0 @@
-#
-#
-# Licensed 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.
-#
-#
-# RPM spec file for the Go version of Traffic Monitor (tm).
-#
-%define debug_package %{nil}
-Name:		traffic_monitor
-Version:        %{traffic_control_version}
-Release:        %{build_number}
-Summary:	Monitor the caches
-Packager:	david_neuman2 at Cable dot Comcast dot com
-Vendor:		Apache Software Foundation
-Group:		Applications/Communications
-License:	Apache License, Version 2.0
-URL:		https://github.com/apache/incubator-trafficcontrol
-Source:		%{_sourcedir}/traffic_monitor-%{traffic_control_version}.tgz
-
-%description
-Installs traffic_monitor
-
-%prep
-
-%setup
-
-%build
-export GOPATH=$(pwd)
-# Create build area with proper gopath structure
-mkdir -p src pkg bin || { echo "Could not create directories in $(pwd): $!"; exit 1; }
-
-go_get_version() {
-  local src=$1
-  local version=$2
-  (
-   cd $src && \
-   git checkout $version && \
-   go get -v \
-  )
-}
-
-# get traffic_ops client
-godir=src/github.com/apache/incubator-trafficcontrol/traffic_ops/client
-( mkdir -p "$godir" && \
-  cd "$godir" && \
-  cp -r "$TC_DIR"/traffic_ops/client/* . && \
-  go get -v \
-) || { echo "Could not build go program at $(pwd): $!"; exit 1; }
-
-#build traffic_monitor binary
-godir=src/github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental
-oldpwd=$(pwd)
-( mkdir -p "$godir" && \
-  cd "$godir" && \
-  cp -r "$TC_DIR"/traffic_monitor/experimental/* . && \
-  cd traffic_monitor && \
-  go get -d -v && \
-  go build -ldflags "-X main.GitRevision=`git rev-parse HEAD` -X main.BuildTimestamp=`date +'%Y-%M-%dT%H:%M:%s'`" \
-) || { echo "Could not build go program at $(pwd): $!"; exit 1; }
-
-%install
-mkdir -p "${RPM_BUILD_ROOT}"/opt/traffic_monitor
-mkdir -p "${RPM_BUILD_ROOT}"/opt/traffic_monitor/bin
-mkdir -p "${RPM_BUILD_ROOT}"/opt/traffic_monitor/conf
-mkdir -p "${RPM_BUILD_ROOT}"/opt/traffic_monitor/backup
-mkdir -p "${RPM_BUILD_ROOT}"/opt/traffic_monitor/static
-mkdir -p "${RPM_BUILD_ROOT}"/opt/traffic_monitor/var/run
-mkdir -p "${RPM_BUILD_ROOT}"/opt/traffic_monitor/var/log
-mkdir -p "${RPM_BUILD_ROOT}"/etc/init.d
-mkdir -p "${RPM_BUILD_ROOT}"/etc/logrotate.d
-
-src=src/github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental
-cp -p "$src"/traffic_monitor/traffic_monitor     "${RPM_BUILD_ROOT}"/opt/traffic_monitor/bin/traffic_monitor
-cp  "$src"/traffic_monitor/static/index.html     "${RPM_BUILD_ROOT}"/opt/traffic_monitor/static/index.html
-cp  "$src"/traffic_monitor/static/sorttable.js     "${RPM_BUILD_ROOT}"/opt/traffic_monitor/static/sorttable.js
-cp "$src"/conf/traffic_ops.cfg        "${RPM_BUILD_ROOT}"/opt/traffic_monitor/conf/traffic_ops.cfg
-cp "$src"/conf/traffic_monitor.cfg        "${RPM_BUILD_ROOT}"/opt/traffic_monitor/conf/traffic_monitor.cfg
-cp "$src"/build/traffic_monitor.init       "${RPM_BUILD_ROOT}"/etc/init.d/traffic_monitor
-cp "$src"/build/traffic_monitor.logrotate  "${RPM_BUILD_ROOT}"/etc/logrotate.d/traffic_monitor
-
-%pre
-/usr/bin/getent group traffic_monitor >/dev/null
-
-if [ $? -ne 0 ]; then
-
-	/usr/sbin/groupadd -g 423 traffic_monitor
-fi
-
-/usr/bin/getent passwd traffic_monitor >/dev/null
-
-if [ $? -ne 0 ]; then
-
-	/usr/sbin/useradd -g traffic_monitor -u 423 -d /opt/traffic_monitor -M traffic_monitor
-
-fi
-
-/usr/bin/passwd -l traffic_monitor >/dev/null
-/usr/bin/chage -E -1 -I -1 -m 0 -M 99999 -W 7 traffic_monitor
-
-if [ -e /etc/init.d/traffic_monitor ]; then
-	/sbin/service traffic_monitor stop
-fi
-
-#don't install over the top of java TM.  This is a workaround since yum doesn't respect the Conflicts tag.
-if [[ $(rpm -q traffic_monitor --qf "%{VERSION}-%{RELEASE}") < 1.9.0 ]]
-then
-    echo -e "\n****************\n"
-    echo "A java version of traffic_monitor is installed.  Please backup/remove that version before installing the golang version of traffic_monitor."
-    echo -e "\n****************\n"
-    exit 1
-fi
-
-%post
-
-/sbin/chkconfig --add traffic_monitor
-/sbin/chkconfig traffic_monitor on
-
-%files
-%defattr(644, traffic_monitor, traffic_monitor, 755)
-%config(noreplace) /opt/traffic_monitor/conf/traffic_monitor.cfg
-%config(noreplace) /opt/traffic_monitor/conf/traffic_ops.cfg
-%config(noreplace) /etc/logrotate.d/traffic_monitor
-
-%dir /opt/traffic_monitor
-%dir /opt/traffic_monitor/bin
-%dir /opt/traffic_monitor/conf
-%dir /opt/traffic_monitor/backup
-%dir /opt/traffic_monitor/static
-%dir /opt/traffic_monitor/var
-%dir /opt/traffic_monitor/var/log
-%dir /opt/traffic_monitor/var/run
-
-%attr(600, traffic_monitor, traffic_monitor) /opt/traffic_monitor/conf/*
-%attr(600, traffic_monitor, traffic_monitor) /opt/traffic_monitor/static/*
-%attr(755, traffic_monitor, traffic_monitor) /opt/traffic_monitor/bin/*
-%attr(755, traffic_monitor, traffic_monitor) /etc/init.d/traffic_monitor
-
-%preun
-# args for hooks: http://www.ibm.com/developerworks/library/l-rpm2/
-# if $1 = 0, this is an uninstallation, if $1 = 1, this is an upgrade (don't do anything)
-if [ "$1" = "0" ]; then
-	/sbin/chkconfig traffic_monitor off
-	/etc/init.d/traffic_monitor stop
-	/sbin/chkconfig --del traffic_monitor
-fi

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/common/adapter/adapter.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/common/adapter/adapter.go b/traffic_monitor/experimental/common/adapter/adapter.go
deleted file mode 100644
index bdb3621..0000000
--- a/traffic_monitor/experimental/common/adapter/adapter.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package adapter
-
-/*
- * 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 (
-	"io"
-)
-
-type Adapter interface {
-	Transform(io.Reader) (interface{}, error)
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/common/crstates/crstates.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/common/crstates/crstates.go b/traffic_monitor/experimental/common/crstates/crstates.go
deleted file mode 100644
index 20e10ca..0000000
--- a/traffic_monitor/experimental/common/crstates/crstates.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package crstates
-
-/*
- * 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.
- */
-
-
-type Cache struct {
-	Name      string `json:"name,omitempty"`
-	Available bool   `json:"isAvailable"`
-}
-
-type DeliveryService struct {
-	Name              string   `json:"name,omitempty"`
-	DisabledLocations []string `json:"disabledLocations"`
-	Available         bool     `json:"isAvailable"`
-}
-
-type CRStates struct {
-	Caches           map[string]Cache           `json:"caches"`
-	DeliveryServices map[string]DeliveryService `json:"deliveryServices"`
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/common/fetcher/fetcher.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/common/fetcher/fetcher.go b/traffic_monitor/experimental/common/fetcher/fetcher.go
deleted file mode 100644
index 6613897..0000000
--- a/traffic_monitor/experimental/common/fetcher/fetcher.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package fetcher
-
-/*
- * 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"
-	"io/ioutil"
-	"net/http"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/handler"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-	"github.com/davecheney/gmx"
-)
-
-type Fetcher interface {
-	Fetch(string, string, uint64, chan<- uint64)
-}
-
-type HttpFetcher struct {
-	Client  *http.Client
-	Headers map[string]string
-	Handler handler.Handler
-	Counters
-}
-
-type Result struct {
-	Source string
-	Data   []byte
-	Error  error
-}
-
-type Counters struct {
-	Success *gmx.Counter
-	Fail    *gmx.Counter
-	Pending *gmx.Gauge
-}
-
-func (f HttpFetcher) Fetch(id string, url string, pollId uint64, pollFinishedChan chan<- uint64) {
-	log.Debugf("poll %v %v fetch start\n", pollId, time.Now())
-	req, err := http.NewRequest("GET", url, nil)
-	// TODO: change this to use f.Headers. -jse
-	req.Header.Set("User-Agent", "traffic_monitor/1.0") // TODO change to 2.0?
-	req.Header.Set("Connection", "keep-alive")
-	if f.Pending != nil {
-		f.Pending.Inc()
-	}
-	startReq := time.Now()
-	response, err := f.Client.Do(req)
-	reqTime := time.Now().Sub(startReq)
-	if f.Pending != nil {
-		f.Pending.Dec()
-	}
-	defer func() {
-		if response != nil && response.Body != nil {
-			ioutil.ReadAll(response.Body) // TODO determine if necessary
-			response.Body.Close()
-		}
-	}()
-
-	if err == nil && response == nil {
-		err = fmt.Errorf("err nil and response nil")
-	}
-	if err == nil && response != nil && (response.StatusCode < 200 || response.StatusCode > 299) {
-		err = fmt.Errorf("bad status: %v", response.StatusCode)
-	}
-	if err != nil {
-		err = fmt.Errorf("id %v url %v fetch error: %v", id, url, err)
-	}
-
-	if err == nil && response != nil {
-		if f.Success != nil {
-			f.Success.Inc()
-		}
-		log.Debugf("poll %v %v fetch end\n", pollId, time.Now())
-		f.Handler.Handle(id, response.Body, reqTime, err, pollId, pollFinishedChan)
-	} else {
-		if f.Fail != nil {
-			f.Fail.Inc()
-		}
-		f.Handler.Handle(id, nil, reqTime, err, pollId, pollFinishedChan)
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/common/handler/handler.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/common/handler/handler.go b/traffic_monitor/experimental/common/handler/handler.go
deleted file mode 100644
index f83ef43..0000000
--- a/traffic_monitor/experimental/common/handler/handler.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package handler
-
-/*
- * 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"
-	"io"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-)
-
-const (
-	NOTIFY_NEVER = iota
-	NOTIFY_CHANGE
-	NOTIFY_ALWAYS
-)
-
-type Handler interface {
-	Handle(string, io.Reader, time.Duration, error, uint64, chan<- uint64)
-}
-
-type OpsConfigFileHandler struct {
-	Content          interface{}
-	ResultChannel    chan interface{}
-	OpsConfigChannel chan OpsConfig
-}
-
-type OpsConfig struct {
-	Username     string `json:"username"`
-	Password     string `json:"password"`
-	Url          string `json:"url"`
-	Insecure     bool   `json:"insecure"`
-	CdnName      string `json:"cdnName"`
-	HttpListener string `json:"httpListener"`
-}
-
-func (handler OpsConfigFileHandler) Listen() {
-	for {
-		result := <-handler.ResultChannel
-		var toc OpsConfig
-
-		err := json.Unmarshal(result.([]byte), &toc)
-
-		if err != nil {
-			log.Errorf("Could not unmarshal Ops Config JSON: %s\n", err)
-		} else {
-			handler.OpsConfigChannel <- toc
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/common/instrumentation/instrumentation.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/common/instrumentation/instrumentation.go b/traffic_monitor/experimental/common/instrumentation/instrumentation.go
deleted file mode 100644
index 12e29b4..0000000
--- a/traffic_monitor/experimental/common/instrumentation/instrumentation.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package instrumentation
-
-/*
- * 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 (
-	"github.com/davecheney/gmx"
-)
-
-var TimerFail *gmx.Counter
-
-func init() {
-	TimerFail = gmx.NewCounter("timerFail")
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/common/log/log.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/common/log/log.go b/traffic_monitor/experimental/common/log/log.go
deleted file mode 100644
index b3b567b..0000000
--- a/traffic_monitor/experimental/common/log/log.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// Inspired by https://www.goinggo.net/2013/11/using-log-package-in-go.html
-package log
-
-/*
- * 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"
-	"io"
-	"log"
-	"time"
-)
-
-var (
-	Debug   *log.Logger
-	Info    *log.Logger
-	Warning *log.Logger
-	Error   *log.Logger
-	Event   *log.Logger
-)
-
-func Init(eventW, errW, warnW, infoW, debugW io.Writer) {
-	Debug = log.New(debugW, "DEBUG: ", log.Lshortfile)
-	Info = log.New(infoW, "INFO: ", log.Lshortfile)
-	Warning = log.New(warnW, "WARNING: ", log.Lshortfile)
-	Error = log.New(errW, "ERROR: ", log.Lshortfile)
-	Event = log.New(eventW, "", 0)
-}
-
-const timeFormat = time.RFC3339Nano
-const stackFrame = 3
-
-func Errorf(format string, v ...interface{}) {
-	Error.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintf(format, v...))
-}
-func Errorln(v ...interface{}) {
-	Error.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintln(v...))
-}
-func Warnf(format string, v ...interface{}) {
-	Warning.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintf(format, v...))
-}
-func Warnln(v ...interface{}) {
-	Warning.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintln(v...))
-}
-func Infof(format string, v ...interface{}) {
-	Info.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintf(format, v...))
-}
-func Infoln(v ...interface{}) {
-	Info.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintln(v...))
-}
-func Debugf(format string, v ...interface{}) {
-	Debug.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintf(format, v...))
-}
-func Debugln(v ...interface{}) {
-	Debug.Output(stackFrame, time.Now().Format(timeFormat)+": "+fmt.Sprintln(v...))
-}
-
-// event log entries (TM event.log, TR access.log, etc)
-func Eventf(t time.Time, format string, v ...interface{}) {
-	// 1484001185.287 ...
-	Event.Printf("%.3f %s", float64(t.Unix())+(float64(t.Nanosecond())/1e9), fmt.Sprintf(format, v...))
-}
-
-// Close calls `Close()` on the given Closer, and logs any error. On error, the context is logged, followed by a colon, the error message, and a newline. This is primarily designed to be used in `defer`, for example, `defer log.Close(resp.Body, "readData fetching /foo/bar")`.
-func Close(c io.Closer, context string) {
-	err := c.Close()
-	if err != nil {
-		Errorf("%v: %v", context, err)
-	}
-}
-
-// Closef acts like Close, with a given format string and values, followed by a colon, the error message, and a newline. The given values are not coerced, concatenated, or printed unless an error occurs, so this is more efficient than `Close()`.
-func Closef(c io.Closer, contextFormat string, v ...interface{}) {
-	err := c.Close()
-	if err != nil {
-		Errorf(contextFormat, v...)
-		Errorf(": %v", err)
-	}
-}
-
-// Write calls `Write()` on the given Writer, and logs any error. On error, the context is logged, followed by a colon, the error message, and a newline.
-func Write(w io.Writer, b []byte, context string) {
-	_, err := w.Write(b)
-	if err != nil {
-		Errorf("%v: %v", context, err)
-	}
-}
-
-// Writef acts like Write, with a given format string and values, followed by a colon, the error message, and a newline. The given values are not coerced, concatenated, or printed unless an error occurs, so this is more efficient than `Write()`.
-func Writef(w io.Writer, b []byte, contextFormat string, v ...interface{}) {
-	_, err := w.Write(b)
-	if err != nil {
-		Errorf(contextFormat, v...)
-		Errorf(": %v", err)
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/common/poller/heap.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/common/poller/heap.go b/traffic_monitor/experimental/common/poller/heap.go
deleted file mode 100644
index e15d61b..0000000
--- a/traffic_monitor/experimental/common/poller/heap.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package poller
-
-/*
- * 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 (
-	"sync"
-	"time"
-)
-
-type HeapPollInfo struct {
-	Info HTTPPollInfo
-	Next time.Time
-}
-
-// Heap implements a Heap from Introduction to Algorithms (Cormen et al). A Heap allows fase access of the maximum object, in this case the latest Next time, and O(log(n)) insert. This Heap is specifically designed to be used as a Priority Queue.
-type Heap struct {
-	m        sync.Mutex
-	info     []HeapPollInfo
-	PollerID int64
-}
-
-func left(i int) int {
-	return 2*i + 1
-}
-
-func right(i int) int {
-	return 2*i + 2
-}
-
-// TODO benchmark directly replacing this, to see if Go inlines the function call
-func parent(i int) int {
-	return (i - 1) / 2
-}
-
-func (h *Heap) heapify(i int) {
-	l := left(i)
-	r := right(i)
-	var largest int
-	if l < len(h.info) && h.info[i].Next.After(h.info[l].Next) {
-		largest = l
-	} else {
-		largest = i
-	}
-
-	if r < len(h.info) && h.info[largest].Next.After(h.info[r].Next) {
-		largest = r
-	}
-
-	if largest != i {
-		h.info[i], h.info[largest] = h.info[largest], h.info[i]
-		h.heapify(largest)
-	}
-}
-
-func (h *Heap) increaseKey(i int, key HeapPollInfo) {
-	if h.info[i].Next.After(key.Next) {
-		panic("Poll.Heap.increaseKey got key smaller than index")
-	}
-
-	h.info[i] = key
-
-	for i > 0 && h.info[parent(i)].Next.After(h.info[i].Next) {
-		h.info[i], h.info[parent(i)] = h.info[parent(i)], h.info[i]
-		i = parent(i)
-	}
-}
-
-// Pop gets the latest time from the heap. Implements Algorithms HEAP-EXTRACT-MAX.
-// Returns the info with the latest time, and false if the heap is empty.
-func (h *Heap) Pop() (HeapPollInfo, bool) {
-	h.m.Lock()
-	defer h.m.Unlock()
-	if len(h.info) == 0 {
-		return HeapPollInfo{}, false
-	}
-	max := h.info[0]
-	h.info[0] = h.info[len(h.info)-1]
-	h.info = h.info[:len(h.info)-1]
-	h.heapify(0)
-	return max, true
-}
-
-// Pop gets the latest time from the heap. Implements Algorithms MAX-HEAP-INSERT.
-func (h *Heap) Push(key HeapPollInfo) {
-	h.m.Lock()
-	defer h.m.Unlock()
-	h.info = append(h.info, HeapPollInfo{Next: time.Unix(1<<63-1, 0)})
-	h.increaseKey(len(h.info)-1, key)
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/common/poller/heap_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/common/poller/heap_test.go b/traffic_monitor/experimental/common/poller/heap_test.go
deleted file mode 100644
index f943c96..0000000
--- a/traffic_monitor/experimental/common/poller/heap_test.go
+++ /dev/null
@@ -1,162 +0,0 @@
-package poller
-
-/*
- * 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"
-	"math/rand"
-	"testing"
-	"time"
-)
-
-func TestHeap(t *testing.T) {
-	h := &Heap{}
-
-	num := 100
-	for i := 0; i < num; i++ {
-		h.Push(HeapPollInfo{
-			Info: HTTPPollInfo{
-				Interval: time.Second * time.Duration(8),
-				ID:       fmt.Sprintf("%v", i),
-			},
-			Next: time.Now().Add(time.Second * time.Duration(i)), // time.Duration((i%2)*-1)
-		})
-	}
-
-	for i := 0; i < num; i++ {
-		val, ok := h.Pop()
-		if !ok {
-			t.Errorf("expected pop ID %v got empty heap", i)
-		} else if val.Info.ID != fmt.Sprintf("%v", i) {
-			t.Errorf("expected pop ID %v got %v next %v", i, val.Info.ID, val.Next)
-		}
-	}
-}
-
-func TestHeapRandom(t *testing.T) {
-	h := &Heap{}
-
-	num := 10
-	for i := 0; i < num; i++ {
-		h.Push(HeapPollInfo{
-			Info: HTTPPollInfo{
-				Interval: time.Second * time.Duration(8),
-				ID:       fmt.Sprintf("%v", i),
-			},
-			Next: time.Now().Add(time.Duration(rand.Int63())),
-		})
-	}
-
-	previousTime := time.Now()
-	for i := 0; i < num; i++ {
-		val, ok := h.Pop()
-		if !ok {
-			t.Errorf("expected pop ID %v got empty heap", i)
-		} else if previousTime.After(val.Next) {
-			t.Errorf("heap pop %v < previous %v expected >", val.Next, previousTime)
-		}
-		previousTime = val.Next
-	}
-}
-
-func TestHeapRandomPopping(t *testing.T) {
-	h := &Heap{}
-
-	randInfo := func(id int) HeapPollInfo {
-		return HeapPollInfo{
-			Info: HTTPPollInfo{
-				Interval: time.Second * time.Duration(8),
-				ID:       fmt.Sprintf("%v", id),
-			},
-			Next: time.Now().Add(time.Duration(rand.Int63())),
-		}
-	}
-
-	num := 10
-	for i := 0; i < num; i++ {
-		h.Push(randInfo(i))
-	}
-
-	previousTime := time.Now()
-	for i := 0; i < num/2; i++ {
-		val, ok := h.Pop()
-		if !ok {
-			t.Errorf("expected pop ID %v got empty heap", i)
-		} else if previousTime.After(val.Next) {
-			t.Errorf("heap pop %v < previous %v expected >", val.Next, previousTime)
-		}
-		previousTime = val.Next
-	}
-
-	for i := 0; i < num; i++ {
-		h.Push(randInfo(i))
-	}
-	val, ok := h.Pop()
-	if !ok {
-		t.Errorf("expected pop, got empty heap")
-	} else {
-		previousTime = val.Next
-	}
-
-	for i := 0; i < num; i++ {
-		val, ok := h.Pop()
-		if !ok {
-			t.Errorf("expected pop ID %v got empty heap", i)
-		} else if previousTime.After(val.Next) {
-			t.Errorf("heap pop %v < previous %v expected >", val.Next, previousTime)
-		}
-		previousTime = val.Next
-	}
-
-	for i := 0; i < num; i++ {
-		h.Push(randInfo(i))
-	}
-	val, ok = h.Pop()
-	if !ok {
-		t.Errorf("expected pop, got empty heap")
-	} else {
-		previousTime = val.Next
-	}
-
-	for i := 0; i < num; i++ {
-		val, ok := h.Pop()
-		if !ok {
-			t.Errorf("expected pop ID %v got empty heap", i)
-		} else if previousTime.After(val.Next) {
-			t.Errorf("heap pop %v < previous %v expected >", val.Next, previousTime)
-		}
-		previousTime = val.Next
-	}
-
-	for i := 0; i < num/2-2; i++ { // -2 for the two we manually popped in order to get the max
-		val, ok := h.Pop()
-		if !ok {
-			t.Errorf("expected pop ID %v got empty heap", i)
-		} else if previousTime.After(val.Next) {
-			t.Errorf("heap pop %v < previous %v expected >", val.Next, previousTime)
-		}
-		previousTime = val.Next
-	}
-
-	val, ok = h.Pop()
-	if ok {
-		t.Errorf("expected empty, got %+v", val)
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/common/poller/poller.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/common/poller/poller.go b/traffic_monitor/experimental/common/poller/poller.go
deleted file mode 100644
index 67f6784..0000000
--- a/traffic_monitor/experimental/common/poller/poller.go
+++ /dev/null
@@ -1,399 +0,0 @@
-package poller
-
-/*
- * 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 (
-	"io/ioutil"
-	"math/rand"
-	"net/http"
-	"os"
-	"sync/atomic"
-	"time"
-
-	"gopkg.in/fsnotify.v1"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/fetcher"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/handler"
-	instr "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/instrumentation"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-	towrap "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/trafficopswrapper" // TODO move to common
-	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
-)
-
-type Poller interface {
-	Poll()
-}
-
-type HttpPoller struct {
-	Config          HttpPollerConfig
-	ConfigChannel   chan HttpPollerConfig
-	FetcherTemplate fetcher.HttpFetcher // FetcherTemplate has all the constant settings, and is copied to create fetchers with custom HTTP client timeouts.
-	TickChan        chan uint64
-}
-
-type PollConfig struct {
-	URL     string
-	Timeout time.Duration
-	Handler handler.Handler
-}
-
-type HttpPollerConfig struct {
-	Urls     map[string]PollConfig
-	Interval time.Duration
-	// noSleep indicates to use the InsomniacPoller. Note this is only used with the initial Poll call, which decides which Poller mechanism to use. After that, this is ignored when the HttpPollerConfig is passed over the ConfigChannel.
-	noSleep bool
-}
-
-// NewHTTP creates and returns a new HttpPoller.
-// If tick is false, HttpPoller.TickChan() will return nil. If noSleep is true, the poller will busywait instead of sleeping, and use a single goroutine which dispatches polls instead of a goroutine per poll.
-func NewHTTP(
-	interval time.Duration,
-	tick bool,
-	httpClient *http.Client,
-	counters fetcher.Counters,
-	fetchHandler handler.Handler,
-	noSleep bool,
-) HttpPoller {
-	var tickChan chan uint64
-	if tick {
-		tickChan = make(chan uint64)
-	}
-	return HttpPoller{
-		TickChan:      tickChan,
-		ConfigChannel: make(chan HttpPollerConfig),
-		Config: HttpPollerConfig{
-			Interval: interval,
-			noSleep:  noSleep,
-		},
-		FetcherTemplate: fetcher.HttpFetcher{
-			Handler:  fetchHandler,
-			Client:   httpClient,
-			Counters: counters,
-		},
-	}
-}
-
-type FilePoller struct {
-	File                string
-	ResultChannel       chan interface{}
-	NotificationChannel chan int
-}
-
-type MonitorConfigPoller struct {
-	Session          towrap.ITrafficOpsSession
-	SessionChannel   chan towrap.ITrafficOpsSession
-	ConfigChannel    chan to.TrafficMonitorConfigMap
-	OpsConfigChannel chan handler.OpsConfig
-	Interval         time.Duration
-	OpsConfig        handler.OpsConfig
-}
-
-// Creates and returns a new HttpPoller.
-// If tick is false, HttpPoller.TickChan() will return nil
-func NewMonitorConfig(interval time.Duration) MonitorConfigPoller {
-	return MonitorConfigPoller{
-		Interval:         interval,
-		SessionChannel:   make(chan towrap.ITrafficOpsSession),
-		ConfigChannel:    make(chan to.TrafficMonitorConfigMap),
-		OpsConfigChannel: make(chan handler.OpsConfig),
-	}
-}
-
-func (p MonitorConfigPoller) Poll() {
-	tick := time.NewTicker(p.Interval)
-	defer tick.Stop()
-	for {
-		select {
-		case opsConfig := <-p.OpsConfigChannel:
-			log.Infof("MonitorConfigPoller: received new opsConfig: %v\n", opsConfig)
-			p.OpsConfig = opsConfig
-		case session := <-p.SessionChannel:
-			log.Infof("MonitorConfigPoller: received new session: %v\n", session)
-			p.Session = session
-		case <-tick.C:
-			if p.Session != nil && p.OpsConfig.CdnName != "" {
-				monitorConfig, err := p.Session.TrafficMonitorConfigMap(p.OpsConfig.CdnName)
-
-				if err != nil {
-					log.Errorf("MonitorConfigPoller: %s\n %v\n", err, monitorConfig)
-				} else {
-					log.Debugln("MonitorConfigPoller: fetched monitorConfig")
-					p.ConfigChannel <- *monitorConfig
-				}
-			} else {
-				log.Warnln("MonitorConfigPoller: skipping this iteration, Session is nil")
-			}
-		}
-	}
-}
-
-var debugPollNum uint64
-
-type HTTPPollInfo struct {
-	Interval time.Duration
-	Timeout  time.Duration
-	ID       string
-	URL      string
-	Handler  handler.Handler
-}
-
-func (p HttpPoller) Poll() {
-	if p.Config.noSleep {
-		log.Debugf("HttpPoller using InsomniacPoll\n")
-		p.InsomniacPoll()
-	} else {
-		log.Debugf("HttpPoller using SleepPoll\n")
-		p.SleepPoll()
-	}
-}
-
-func (p HttpPoller) SleepPoll() {
-	// iterationCount := uint64(0)
-	// iterationCount++ // on tick<:
-	// case p.TickChan <- iterationCount:
-	killChans := map[string]chan<- struct{}{}
-	for newConfig := range p.ConfigChannel {
-		deletions, additions := diffConfigs(p.Config, newConfig)
-		for _, id := range deletions {
-			killChan := killChans[id]
-			go func() { killChan <- struct{}{} }() // go - we don't want to wait for old polls to die.
-			delete(killChans, id)
-		}
-		for _, info := range additions {
-			kill := make(chan struct{})
-			killChans[info.ID] = kill
-
-			fetcher := p.FetcherTemplate
-			if info.Timeout != 0 { // if the timeout isn't explicitly set, use the template value.
-				c := *fetcher.Client
-				fetcher.Client = &c // copy the client, so we don't change other fetchers.
-				fetcher.Client.Timeout = info.Timeout
-			}
-			go sleepPoller(info.Interval, info.ID, info.URL, fetcher, kill)
-		}
-		p.Config = newConfig
-	}
-}
-
-func mustDie(die <-chan struct{}) bool {
-	select {
-	case <-die:
-		return true
-	default:
-	}
-	return false
-}
-
-// TODO iterationCount and/or p.TickChan?
-func sleepPoller(interval time.Duration, id string, url string, fetcher fetcher.Fetcher, die <-chan struct{}) {
-	pollSpread := time.Duration(rand.Float64()*float64(interval/time.Nanosecond)) * time.Nanosecond
-	time.Sleep(pollSpread)
-	tick := time.NewTicker(interval)
-	lastTime := time.Now()
-	for {
-		select {
-		case <-tick.C:
-			realInterval := time.Now().Sub(lastTime)
-			if realInterval > interval+(time.Millisecond*100) {
-				instr.TimerFail.Inc()
-				log.Debugf("Intended Duration: %v Actual Duration: %v\n", interval, realInterval)
-			}
-			lastTime = time.Now()
-
-			pollId := atomic.AddUint64(&debugPollNum, 1)
-			pollFinishedChan := make(chan uint64)
-			log.Debugf("poll %v %v start\n", pollId, time.Now())
-			go fetcher.Fetch(id, url, pollId, pollFinishedChan) // TODO persist fetcher, with its own die chan?
-			<-pollFinishedChan
-		case <-die:
-			tick.Stop()
-			return
-		}
-	}
-}
-
-const InsomniacPollerEmptySleepDuration = time.Millisecond * time.Duration(100)
-
-// InsomniacPoll polls using a single thread, which never sleeps. This exists to work around a bug observed in OpenStack CentOS 6.5 kernel 2.6.32 wherin sleep gets progressively slower. This should be removed and Poll() changed to call SleepPoll() when the bug is tracked down and fixed for production.
-func (p HttpPoller) InsomniacPoll() {
-	// iterationCount := uint64(0)
-	// iterationCount++ // on tick<:
-	// case p.TickChan <- iterationCount:
-	killChan := make(chan struct{})
-	pollRunning := false // TODO find less awkward way to not kill the first loop
-	pollerId := rand.Int63()
-	for newCfg := range p.ConfigChannel {
-		// TODO add a more efficient function than diffConfigs for this func, since we only need to know whether anything changed
-		deletions, additions := diffConfigs(p.Config, newCfg)
-		if len(deletions) == 0 && len(additions) == 0 {
-			continue
-		}
-
-		if pollRunning {
-			killChan <- struct{}{}
-		}
-		pollRunning = true
-
-		polls := []HTTPPollInfo{}
-		for id, pollCfg := range newCfg.Urls {
-			polls = append(polls, HTTPPollInfo{
-				Interval: newCfg.Interval - InsomniacPollerEmptySleepDuration,
-				ID:       id,
-				URL:      pollCfg.URL,
-				Timeout:  pollCfg.Timeout,
-			})
-		}
-		go insomniacPoller(pollerId, polls, p.FetcherTemplate, killChan)
-		p.Config = newCfg
-	}
-}
-
-func insomniacPoller(pollerId int64, polls []HTTPPollInfo, fetcherTemplate fetcher.HttpFetcher, die <-chan struct{}) {
-	heap := Heap{PollerID: pollerId}
-	start := time.Now()
-	fetchers := map[string]fetcher.Fetcher{}
-	for _, p := range polls {
-		spread := time.Duration(rand.Float64()*float64(p.Interval/time.Nanosecond)) * time.Nanosecond
-		heap.Push(HeapPollInfo{Info: p, Next: start.Add(spread)})
-
-		fetcher := fetcherTemplate
-		if p.Timeout != 0 { // if the timeout isn't explicitly set, use the template value.
-			c := *fetcher.Client
-			fetcher.Client = &c // copy the client, so we don't change other fetchers.
-			fetcher.Client.Timeout = p.Timeout
-		}
-		fetchers[p.ID] = fetcher
-	}
-
-	timeMax := func(a time.Time, b time.Time) time.Time {
-		if a.After(b) {
-			return a
-		}
-		return b
-	}
-
-	poll := func(p HeapPollInfo) {
-		start := time.Now()
-		pollId := atomic.AddUint64(&debugPollNum, 1)
-		// TODO change pollFinishedChan to callback, for performance
-		pollFinishedChan := make(chan uint64)
-
-		go fetchers[p.Info.ID].Fetch(p.Info.ID, p.Info.URL, pollId, pollFinishedChan) // TODO persist fetcher, with its own die chan?
-		<-pollFinishedChan
-		now := time.Now()
-		p.Next = timeMax(start.Add(p.Info.Interval), now)
-		heap.Push(p)
-	}
-
-	for {
-		p, ok := heap.Pop()
-		if !ok {
-			ThreadSleep(InsomniacPollerEmptySleepDuration)
-			continue
-		}
-		if mustDie(die) {
-			return
-		}
-		ThreadSleep(p.Next.Sub(time.Now()))
-		go poll(p)
-	}
-}
-
-func (p FilePoller) Poll() {
-	// initial read before watching for changes
-	contents, err := ioutil.ReadFile(p.File)
-
-	if err != nil {
-		log.Errorf("reading %s: %s\n", p.File, err)
-		os.Exit(1) // TODO: this is a little drastic -jse
-	} else {
-		p.ResultChannel <- contents
-	}
-
-	watcher, _ := fsnotify.NewWatcher()
-	watcher.Add(p.File)
-
-	for {
-		select {
-		case event := <-watcher.Events:
-			if event.Op&fsnotify.Write == fsnotify.Write {
-				contents, err := ioutil.ReadFile(p.File)
-
-				if err != nil {
-					log.Errorf("opening %s: %s\n", p.File, err)
-				} else {
-					p.ResultChannel <- contents
-				}
-			}
-		case err := <-watcher.Errors:
-			log.Errorln(time.Now(), "error:", err)
-		}
-	}
-}
-
-// diffConfigs takes the old and new configs, and returns a list of deleted IDs, and a list of new polls to do
-func diffConfigs(old HttpPollerConfig, new HttpPollerConfig) ([]string, []HTTPPollInfo) {
-	deletions := []string{}
-	additions := []HTTPPollInfo{}
-
-	if old.Interval != new.Interval {
-		for id, _ := range old.Urls {
-			deletions = append(deletions, id)
-		}
-		for id, pollCfg := range new.Urls {
-			additions = append(additions, HTTPPollInfo{
-				Interval: new.Interval,
-				ID:       id,
-				URL:      pollCfg.URL,
-				Timeout:  pollCfg.Timeout,
-			})
-		}
-		return deletions, additions
-	}
-
-	for id, oldPollCfg := range old.Urls {
-		newPollCfg, newIdExists := new.Urls[id]
-		if !newIdExists {
-			deletions = append(deletions, id)
-		} else if newPollCfg != oldPollCfg {
-			deletions = append(deletions, id)
-			additions = append(additions, HTTPPollInfo{
-				Interval: new.Interval,
-				ID:       id,
-				URL:      newPollCfg.URL,
-				Timeout:  newPollCfg.Timeout,
-			})
-		}
-	}
-
-	for id, newPollCfg := range new.Urls {
-		_, oldIdExists := old.Urls[id]
-		if !oldIdExists {
-			additions = append(additions, HTTPPollInfo{
-				Interval: new.Interval,
-				ID:       id,
-				URL:      newPollCfg.URL,
-				Timeout:  newPollCfg.Timeout,
-			})
-		}
-	}
-
-	return deletions, additions
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/common/poller/threadsleep.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/common/poller/threadsleep.go b/traffic_monitor/experimental/common/poller/threadsleep.go
deleted file mode 100644
index d4a67cc..0000000
--- a/traffic_monitor/experimental/common/poller/threadsleep.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// +build !linux
-
-/*
- * 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.
- */
-
-package poller
-
-import (
-	"runtime"
-	"time"
-)
-
-// ThreadSleep actually busywaits for the given duration. This is becuase Go doesn't have Mac and Windows nanosleep syscalls, and `Sleep` sleeps for progressively longer than requested.
-func ThreadSleep(d time.Duration) {
-	// TODO fix to not busywait on Mac, Windows. We can't simply Sleep, because Sleep gets progressively slower as the app runs, due to a Go runtime issue. If this is changed, you MUST verify the poll doesn't get slower after the app runs for several days.
-	end := time.Now().Add(d)
-	for end.After(time.Now()) {
-		runtime.Gosched()
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/common/poller/threadsleep_linux.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/common/poller/threadsleep_linux.go b/traffic_monitor/experimental/common/poller/threadsleep_linux.go
deleted file mode 100644
index ba7c379..0000000
--- a/traffic_monitor/experimental/common/poller/threadsleep_linux.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// +build linux
-
-/*
- * 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.
- */
-
-package poller
-
-import (
-	"errors"
-	"golang.org/x/sys/unix"
-	"time"
-)
-
-// ThreadSleep sleeps using the POSIX syscall `nanosleep`. Note this does not sleep the goroutine, but the operating system thread itself. This should only be called by a goroutine which has previously called `LockOSThread`. This exists due to a bug with `time.Sleep` getting progressively slower as the app runs, and should be removed if the bug in Go is fixed.
-func ThreadSleep(d time.Duration) {
-	if d < 0 {
-		d = 0
-	}
-	t := unix.Timespec{}
-	leftover := unix.NsecToTimespec(d.Nanoseconds())
-	err := errors.New("")
-	for err != nil && (leftover.Sec != 0 || leftover.Nsec != 0) {
-		t = leftover
-		err = unix.Nanosleep(&t, &leftover)
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/common/util/join.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/common/util/join.go b/traffic_monitor/experimental/common/util/join.go
deleted file mode 100644
index d5b647b..0000000
--- a/traffic_monitor/experimental/common/util/join.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package util
-
-/*
- * 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"
-)
-
-func JoinErrorsString(errs []error) string {
-	joined := JoinErrors(errs)
-
-	if joined == nil {
-		return ""
-	}
-
-	return joined.Error()
-}
-
-func JoinErrors(errs []error) error {
-	return JoinErrorsSep(errs, "")
-}
-
-func JoinErrorsSep(errs []error, separator string) error {
-	if separator == "" {
-		separator = ", "
-	}
-
-	joinedErrors := ""
-
-	for _, err := range errs {
-		if err != nil {
-			joinedErrors += err.Error() + separator
-		}
-	}
-
-	if len(joinedErrors) == 0 {
-		return nil
-	}
-
-	joinedErrors = joinedErrors[:len(joinedErrors)-len(separator)] // strip trailing separator
-
-	return fmt.Errorf("%s", joinedErrors)
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/common/util/num.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/common/util/num.go b/traffic_monitor/experimental/common/util/num.go
deleted file mode 100644
index 4db2cf9..0000000
--- a/traffic_monitor/experimental/common/util/num.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package util
-
-// 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?
-func ToNumeric(v interface{}) (float64, bool) {
-	switch i := v.(type) {
-	case uint8:
-		return float64(i), true
-	case uint16:
-		return float64(i), true
-	case uint32:
-		return float64(i), true
-	case uint64:
-		return float64(i), true
-	case int8:
-		return float64(i), true
-	case int16:
-		return float64(i), true
-	case int32:
-		return float64(i), true
-	case int64:
-		return float64(i), true
-	case float32:
-		return float64(i), true
-	case float64:
-		return i, true
-	case int:
-		return float64(i), true
-	case uint:
-		return float64(i), true
-	default:
-		return 0.0, false
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/conf/traffic_monitor.cfg
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/conf/traffic_monitor.cfg b/traffic_monitor/experimental/conf/traffic_monitor.cfg
deleted file mode 100644
index f6698d1..0000000
--- a/traffic_monitor/experimental/conf/traffic_monitor.cfg
+++ /dev/null
@@ -1,22 +0,0 @@
-{
-	"cache_health_polling_interval_ms": 6000,
-	"cache_stat_polling_interval_ms": 6000,
-	"monitor_config_polling_interval_ms": 5000,
-	"http_timeout_ms": 2000,
-	"peer_polling_interval_ms": 5000,
-	"peer_optimistic": true,
-	"max_events": 200,
-	"max_stat_history": 5,
-	"max_health_history": 5,
-	"health_flush_interval_ms": 20,
-	"stat_flush_interval_ms": 20,
-	"log_location_event": "null",
-	"log_location_error": "stderr",
-	"log_location_warning": "stdout",
-	"log_location_info": "null",
-	"log_location_debug": "null",
-	"serve_read_timeout_ms": 10000,
-	"serve_write_timeout_ms": 10000,
-	"http_poll_no_sleep": false,
-	"static_file_dir": "/opt/traffic_monitor/static/"
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/conf/traffic_ops.cfg
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/conf/traffic_ops.cfg b/traffic_monitor/experimental/conf/traffic_ops.cfg
deleted file mode 100644
index b5fcf83..0000000
--- a/traffic_monitor/experimental/conf/traffic_ops.cfg
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-	"username": "",
-	"password": "",
-	"url": "https://traffic-ops.example.net",
-	"insecure": true,
-	"cdnName": "cdn",
-	"httpListener": ":8080"
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/README.md
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/README.md b/traffic_monitor/experimental/traffic_monitor/README.md
deleted file mode 100644
index c3534fb..0000000
--- a/traffic_monitor/experimental/traffic_monitor/README.md
+++ /dev/null
@@ -1 +0,0 @@
-To run: `./build.sh && traffic_monitor --opsCfg ./traffic_ops.cfg -config ./traffic_monitor-example-config.json`

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/build.sh
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/build.sh b/traffic_monitor/experimental/traffic_monitor/build.sh
deleted file mode 100755
index baf07cc..0000000
--- a/traffic_monitor/experimental/traffic_monitor/build.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-# 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.
-#!/usr/bin/env bash
-go build -ldflags "-X main.GitRevision=`git rev-parse HEAD` -X main.BuildTimestamp=`date +'%Y-%M-%dT%H:%M:%S'`"

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/cache/astats.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/cache/astats.go b/traffic_monitor/experimental/traffic_monitor/cache/astats.go
deleted file mode 100644
index 96258c8..0000000
--- a/traffic_monitor/experimental/traffic_monitor/cache/astats.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package cache
-
-/*
- * 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"
-)
-
-// Astats contains ATS data returned from the Astats ATS plugin. This includes generic stats, as well as fixed system stats.
-type Astats struct {
-	Ats    map[string]interface{} `json:"ats"`
-	System AstatsSystem           `json:"system"`
-}
-
-// AstatsSystem represents fixed system stats returne from ATS by the Astats plugin.
-type AstatsSystem struct {
-	InfName           string `json:"inf.name"`
-	InfSpeed          int    `json:"inf.speed"`
-	ProcNetDev        string `json:"proc.net.dev"`
-	ProcLoadavg       string `json:"proc.loadavg"`
-	ConfigLoadRequest int    `json:"configReloadRequests"`
-	LastReloadRequest int    `json:"lastReloadRequest"`
-	ConfigReloads     int    `json:"configReloads"`
-	LastReload        int    `json:"lastReload"`
-	AstatsLoad        int    `json:"astatsLoad"`
-}
-
-// Unmarshal unmarshalls the given bytes, which must be JSON Astats data, into an Astats object.
-func Unmarshal(body []byte) (Astats, error) {
-	var aStats Astats
-	err := json.Unmarshal(body, &aStats)
-	return aStats, err
-}


[09/19] incubator-trafficcontrol git commit: Move TM2 to trafficcontrol/traffic_monitor_golang

Posted by ne...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/cache/astats.json
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/cache/astats.json b/traffic_monitor_golang/traffic_monitor/cache/astats.json
new file mode 100644
index 0000000..df0e9d7
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/cache/astats.json
@@ -0,0 +1,531 @@
+{ "ats": {
+   "proxy.process.http.completed_requests": 26220072200,
+   "proxy.process.http.total_incoming_connections": 770802777,
+   "proxy.process.http.total_client_connections": 770802777,
+   "proxy.process.http.total_client_connections_ipv7": 7706760272,
+   "proxy.process.http.total_client_connections_ipv6": 2067066,
+   "proxy.process.http.total_server_connections": 77676797,
+   "proxy.process.http.total_parent_proxy_connections": 26072792,
+   "proxy.process.http.avg_transactions_per_client_connection": .67907,
+   "proxy.process.http.avg_transactions_per_server_connection": 7.090202,
+   "proxy.process.http.avg_transactions_per_parent_connection": 0.000000,
+   "proxy.process.http.client_connection_time": 0,
+   "proxy.process.http.parent_proxy_connection_time": 0,
+   "proxy.process.http.server_connection_time": 0,
+   "proxy.process.http.cache_connection_time": 0,
+   "proxy.process.http.transaction_counts.errors.pre_accept_hangups": 0,
+   "proxy.process.http.transaction_totaltime.errors.pre_accept_hangups": 0.000000,
+   "proxy.process.http.transaction_counts.errors.empty_hangups": 0,
+   "proxy.process.http.transaction_totaltime.errors.empty_hangups": 0.000000,
+   "proxy.process.http.transaction_counts.errors.early_hangups": 0,
+   "proxy.process.http.transaction_totaltime.errors.early_hangups": 0.000000,
+   "proxy.process.http.incoming_requests": 26202677,
+   "proxy.process.http.outgoing_requests": 90660,
+   "proxy.process.http.incoming_responses": 9222007,
+   "proxy.process.http.invalid_client_requests": 7277,
+   "proxy.process.http.missing_host_hdr": 0,
+   "proxy.process.http.get_requests": 26202090676,
+   "proxy.process.http.head_requests": 277,
+   "proxy.process.http.trace_requests": 0,
+   "proxy.process.http.options_requests": 76,
+   "proxy.process.http.post_requests": 0,
+   "proxy.process.http.put_requests": 0,
+   "proxy.process.http.push_requests": 0,
+   "proxy.process.http.delete_requests": 0,
+   "proxy.process.http.purge_requests": 2072,
+   "proxy.process.http.connect_requests": 0,
+   "proxy.process.http.extension_method_requests": 226,
+   "proxy.process.http.client_no_cache_requests": 0,
+   "proxy.process.http.broken_server_connections": 20890,
+   "proxy.process.http.cache_lookups": 2608970298,
+   "proxy.process.http.cache_writes": 9292970,
+   "proxy.process.http.cache_updates": 22829209,
+   "proxy.process.http.cache_deletes": 6682,
+   "proxy.process.http.tunnels": 2022022,
+   "proxy.process.http.throttled_proxy_only": 0,
+   "proxy.process.http.request_taxonomy.i0_n0_m0": 0,
+   "proxy.process.http.request_taxonomy.i2_n0_m0": 0,
+   "proxy.process.http.request_taxonomy.i0_n2_m0": 0,
+   "proxy.process.http.request_taxonomy.i2_n2_m0": 0,
+   "proxy.process.http.request_taxonomy.i0_n0_m2": 0,
+   "proxy.process.http.request_taxonomy.i2_n0_m2": 0,
+   "proxy.process.http.request_taxonomy.i0_n2_m2": 0,
+   "proxy.process.http.request_taxonomy.i2_n2_m2": 0,
+   "proxy.process.http.icp_suggested_lookups": 0,
+   "proxy.process.http.client_transaction_time": 0,
+   "proxy.process.http.client_write_time": 0,
+   "proxy.process.http.server_read_time": 0,
+   "proxy.process.http.icp_transaction_time": 0,
+   "proxy.process.http.icp_raw_transaction_time": 0,
+   "proxy.process.http.parent_proxy_transaction_time": 279292829060726822,
+   "proxy.process.http.parent_proxy_raw_transaction_time": 0,
+   "proxy.process.http.server_transaction_time": 0,
+   "proxy.process.http.server_raw_transaction_time": 0,
+   "proxy.process.http.user_agent_request_header_total_size": 727722927268,
+   "proxy.process.http.user_agent_response_header_total_size": 8822770068882,
+   "proxy.process.http.user_agent_request_document_total_size": 26220,
+   "proxy.process.http.user_agent_response_document_total_size": 8700277270087,
+   "proxy.process.http.origin_server_request_header_total_size": 270877627,
+   "proxy.process.http.origin_server_response_header_total_size": 99929980,
+   "proxy.process.http.origin_server_request_document_total_size": 26220,
+   "proxy.process.http.origin_server_response_document_total_size": 2606976709670,
+   "proxy.process.http.parent_proxy_request_total_bytes": 20092976007,
+   "proxy.process.http.parent_proxy_response_total_bytes": 28668060280722,
+   "proxy.process.http.pushed_response_header_total_size": 0,
+   "proxy.process.http.pushed_document_total_size": 0,
+   "proxy.process.http.response_document_size_200": 276200702,
+   "proxy.process.http.response_document_size_2K": 2870679,
+   "proxy.process.http.response_document_size_K": 7777727978,
+   "proxy.process.http.response_document_size_0K": 2706887708,
+   "proxy.process.http.response_document_size_20K": 8727207,
+   "proxy.process.http.response_document_size_2M": 0967270687,
+   "proxy.process.http.response_document_size_inf": 22928972,
+   "proxy.process.http.request_document_size_200": 26220072072,
+   "proxy.process.http.request_document_size_2K": 227,
+   "proxy.process.http.request_document_size_K": 0,
+   "proxy.process.http.request_document_size_0K": 0,
+   "proxy.process.http.request_document_size_20K": 0,
+   "proxy.process.http.request_document_size_2M": 0,
+   "proxy.process.http.request_document_size_inf": 0,
+   "proxy.process.http.user_agent_speed_bytes_per_sec_200": 228020707,
+   "proxy.process.http.user_agent_speed_bytes_per_sec_2K": 277,
+   "proxy.process.http.user_agent_speed_bytes_per_sec_20K": 2976266,
+   "proxy.process.http.user_agent_speed_bytes_per_sec_200K": 790027,
+   "proxy.process.http.user_agent_speed_bytes_per_sec_2M": 90079277,
+   "proxy.process.http.user_agent_speed_bytes_per_sec_20M": 62029028,
+   "proxy.process.http.user_agent_speed_bytes_per_sec_200M": 229077080,
+   "proxy.process.http.origin_server_speed_bytes_per_sec_200": 20200,
+   "proxy.process.http.origin_server_speed_bytes_per_sec_2K": 29,
+   "proxy.process.http.origin_server_speed_bytes_per_sec_20K": 2820,
+   "proxy.process.http.origin_server_speed_bytes_per_sec_200K": 29020,
+   "proxy.process.http.origin_server_speed_bytes_per_sec_2M": 2680770,
+   "proxy.process.http.origin_server_speed_bytes_per_sec_20M": 72272800,
+   "proxy.process.http.origin_server_speed_bytes_per_sec_200M": 0969207,
+   "proxy.process.http.total_transactions_time": 7760708772270296008,
+   "proxy.process.http.total_transactions_think_time": 0,
+   "proxy.process.http.cache_hit_fresh": 2077707982,
+   "proxy.process.http.cache_hit_mem_fresh": 0,
+   "proxy.process.http.cache_hit_revalidated": 229007880,
+   "proxy.process.http.cache_hit_ims": 2262288,
+   "proxy.process.http.cache_hit_stale_served": 7,
+   "proxy.process.http.cache_miss_cold": 9867272,
+   "proxy.process.http.cache_miss_changed": 860002,
+   "proxy.process.http.cache_miss_client_no_cache": 0,
+   "proxy.process.http.cache_miss_client_not_cacheable": 20220202,
+   "proxy.process.http.cache_miss_ims": 78790,
+   "proxy.process.http.cache_read_error": 0,
+   "proxy.process.http.tcp_hit_count_stat": 2077707982,
+   "proxy.process.http.tcp_hit_user_agent_bytes_stat": 702708722077027,
+   "proxy.process.http.tcp_hit_origin_server_bytes_stat": 0,
+   "proxy.process.http.tcp_miss_count_stat": 208776270,
+   "proxy.process.http.tcp_miss_user_agent_bytes_stat": 2072208728029,
+   "proxy.process.http.tcp_miss_origin_server_bytes_stat": 207028678070,
+   "proxy.process.http.tcp_expired_miss_count_stat": 0,
+   "proxy.process.http.tcp_expired_miss_user_agent_bytes_stat": 0,
+   "proxy.process.http.tcp_expired_miss_origin_server_bytes_stat": 0,
+   "proxy.process.http.tcp_refresh_hit_count_stat": 229007880,
+   "proxy.process.http.tcp_refresh_hit_user_agent_bytes_stat": 8799628807970,
+   "proxy.process.http.tcp_refresh_hit_origin_server_bytes_stat": 2762670767,
+   "proxy.process.http.tcp_refresh_miss_count_stat": 860002,
+   "proxy.process.http.tcp_refresh_miss_user_agent_bytes_stat": 28727862207,
+   "proxy.process.http.tcp_refresh_miss_origin_server_bytes_stat": 2876070272,
+   "proxy.process.http.tcp_client_refresh_count_stat": 0,
+   "proxy.process.http.tcp_client_refresh_user_agent_bytes_stat": 0,
+   "proxy.process.http.tcp_client_refresh_origin_server_bytes_stat": 0,
+   "proxy.process.http.tcp_ims_hit_count_stat": 2262288,
+   "proxy.process.http.tcp_ims_hit_user_agent_bytes_stat": 060080760226,
+   "proxy.process.http.tcp_ims_hit_origin_server_bytes_stat": 0,
+   "proxy.process.http.tcp_ims_miss_count_stat": 78790,
+   "proxy.process.http.tcp_ims_miss_user_agent_bytes_stat": 2000222026,
+   "proxy.process.http.tcp_ims_miss_origin_server_bytes_stat": 207297027,
+   "proxy.process.http.err_client_abort_count_stat": 20702,
+   "proxy.process.http.err_client_abort_user_agent_bytes_stat": 22679227077728,
+   "proxy.process.http.err_client_abort_origin_server_bytes_stat": 29787270727,
+   "proxy.process.http.err_connect_fail_count_stat": 27278,
+   "proxy.process.http.err_connect_fail_user_agent_bytes_stat": 07692,
+   "proxy.process.http.err_connect_fail_origin_server_bytes_stat": 70772,
+   "proxy.process.http.misc_count_stat": 20729986,
+   "proxy.process.http.misc_user_agent_bytes_stat": 2790267,
+   "proxy.process.http.background_fill_bytes_aborted_stat": 0,
+   "proxy.process.http.background_fill_bytes_completed_stat": 0,
+   "proxy.process.http.cache_write_errors": 0,
+   "proxy.process.http.cache_read_errors": 0,
+   "proxy.process.http.200_responses": 0,
+   "proxy.process.http.202_responses": 0,
+   "proxy.process.http.2xx_responses": 0,
+   "proxy.process.http.200_responses": 2096207270,
+   "proxy.process.http.202_responses": 0,
+   "proxy.process.http.202_responses": 0,
+   "proxy.process.http.20_responses": 0,
+   "proxy.process.http.207_responses": 0,
+   "proxy.process.http.200_responses": 0,
+   "proxy.process.http.206_responses": 2808,
+   "proxy.process.http.2xx_responses": 2096208977,
+   "proxy.process.http.00_responses": 0,
+   "proxy.process.http.02_responses": ,
+   "proxy.process.http.02_responses": 670,
+   "proxy.process.http.0_responses": 0,
+   "proxy.process.http.07_responses": 228770062,
+   "proxy.process.http.00_responses": 0,
+   "proxy.process.http.07_responses": 0,
+   "proxy.process.http.xx_responses": 228770709,
+   "proxy.process.http.700_responses": 2,
+   "proxy.process.http.702_responses": 0,
+   "proxy.process.http.702_responses": 0,
+   "proxy.process.http.70_responses": 7022,
+   "proxy.process.http.707_responses": 00029,
+   "proxy.process.http.700_responses": 227,
+   "proxy.process.http.706_responses": 0,
+   "proxy.process.http.707_responses": 0,
+   "proxy.process.http.708_responses": 0,
+   "proxy.process.http.709_responses": 0,
+   "proxy.process.http.720_responses": 708,
+   "proxy.process.http.722_responses": 0,
+   "proxy.process.http.722_responses": 0,
+   "proxy.process.http.72_responses": 0,
+   "proxy.process.http.727_responses": 0,
+   "proxy.process.http.720_responses": 22897,
+   "proxy.process.http.726_responses": 27,
+   "proxy.process.http.7xx_responses": 000722,
+   "proxy.process.http.000_responses": 20260,
+   "proxy.process.http.002_responses": 2,
+   "proxy.process.http.002_responses": 29998,
+   "proxy.process.http.00_responses": 8222,
+   "proxy.process.http.007_responses": 0,
+   "proxy.process.http.000_responses": 0,
+   "proxy.process.http.0xx_responses": 220222,
+   "proxy.process.http.transaction_counts.hit_fresh": 2070960080,
+   "proxy.process.http.transaction_totaltime.hit_fresh": 609727688.000000,
+   "proxy.process.http.transaction_counts.hit_fresh.process": 2070960080,
+   "proxy.process.http.transaction_totaltime.hit_fresh.process": 6097982700.000000,
+   "proxy.process.http.transaction_counts.hit_revalidated": 229007880,
+   "proxy.process.http.transaction_totaltime.hit_revalidated": 20720780.000000,
+   "proxy.process.http.transaction_counts.miss_cold": 99007787,
+   "proxy.process.http.transaction_totaltime.miss_cold": 866268.000000,
+   "proxy.process.http.transaction_counts.miss_not_cacheable": 20220202,
+   "proxy.process.http.transaction_totaltime.miss_not_cacheable": 6000.077922,
+   "proxy.process.http.transaction_counts.miss_changed": 860002,
+   "proxy.process.http.transaction_totaltime.miss_changed": 86002.220000,
+   "proxy.process.http.transaction_counts.miss_client_no_cache": 0,
+   "proxy.process.http.transaction_totaltime.miss_client_no_cache": 0.000000,
+   "proxy.process.http.transaction_counts.errors.aborts": 28770207,
+   "proxy.process.http.transaction_totaltime.errors.aborts": 727069770.000000,
+   "proxy.process.http.transaction_counts.errors.possible_aborts": 0,
+   "proxy.process.http.transaction_totaltime.errors.possible_aborts": 0.000000,
+   "proxy.process.http.transaction_counts.errors.connect_failed": 27278,
+   "proxy.process.http.transaction_totaltime.errors.connect_failed": 09992.000000,
+   "proxy.process.http.transaction_counts.errors.other": 78826,
+   "proxy.process.http.transaction_totaltime.errors.other": 660.627288,
+   "proxy.process.http.transaction_counts.other.unclassified": 0,
+   "proxy.process.http.transaction_totaltime.other.unclassified": 0.000000,
+   "proxy.process.http.total_x_redirect_count": 0,
+   "proxy.process.net.net_handler_run": 20786009,
+   "proxy.process.net.read_bytes": 66227787609,
+   "proxy.process.net.write_bytes": 8872762770970,
+   "proxy.process.net.calls_to_readfromnet": 0,
+   "proxy.process.net.calls_to_readfromnet_afterpoll": 0,
+   "proxy.process.net.calls_to_read": 0,
+   "proxy.process.net.calls_to_read_nodata": 0,
+   "proxy.process.net.calls_to_writetonet": 0,
+   "proxy.process.net.calls_to_writetonet_afterpoll": 0,
+   "proxy.process.net.calls_to_write": 0,
+   "proxy.process.net.calls_to_write_nodata": 0,
+   "proxy.process.socks.connections_successful": 0,
+   "proxy.process.socks.connections_unsuccessful": 0,
+   "proxy.process.cache.read_per_sec": 26.98027,
+   "proxy.process.cache.write_per_sec": 2.09770,
+   "proxy.process.cache.KB_read_per_sec": 7879.200879,
+   "proxy.process.cache.KB_write_per_sec": 07.826272,
+   "proxy.process.hostdb.total_entries": 20000,
+   "proxy.process.hostdb.total_lookups": 6727907,
+   "proxy.process.hostdb.ttl": 0.000000,
+   "proxy.process.hostdb.ttl_expires": 668872,
+   "proxy.process.hostdb.re_dns_on_reload": 0,
+   "proxy.process.hostdb.bytes": 2090872,
+   "proxy.process.dns.total_dns_lookups": 29972722,
+   "proxy.process.dns.lookup_avg_time": 0,
+   "proxy.process.dns.lookup_successes": 722789,
+   "proxy.process.dns.fail_avg_time": 0,
+   "proxy.process.dns.lookup_failures": 077766,
+   "proxy.process.dns.retries": 2772,
+   "proxy.process.dns.max_retries_exceeded": 20,
+   "proxy.process.log.bytes_written_to_disk": 2689728227,
+   "proxy.process.log.bytes_sent_to_network": 0,
+   "proxy.process.log.bytes_received_from_network": 0,
+   "proxy.process.log.event_log_access_fail": 0,
+   "proxy.process.log.event_log_access_skip": 0,
+   "proxy.process.net.inactivity_cop_lock_acquire_failure": 2782,
+   "proxy.process.log.event_log_error_ok": 27627,
+   "proxy.process.log.event_log_error_skip": 0,
+   "proxy.process.log.event_log_error_aggr": 0,
+   "proxy.process.log.event_log_error_full": 0,
+   "proxy.process.log.event_log_error_fail": 0,
+   "proxy.process.log.event_log_access_ok": 770722262,
+   "proxy.process.log.event_log_access_aggr": 0,
+   "proxy.process.log.event_log_access_full": 0,
+   "proxy.process.log.num_sent_to_network": 0,
+   "proxy.process.log.num_lost_before_sent_to_network": 0,
+   "proxy.process.log.num_received_from_network": 0,
+   "proxy.process.log.num_flush_to_disk": 770729280,
+   "proxy.process.log.num_lost_before_flush_to_disk": 0,
+   "proxy.process.log.bytes_lost_before_preproc": 0,
+   "proxy.process.log.bytes_lost_before_sent_to_network": 0,
+   "proxy.process.log.bytes_flush_to_disk": 2689728227,
+   "proxy.process.log.bytes_lost_before_flush_to_disk": 0,
+   "proxy.process.log.bytes_lost_before_written_to_disk": 0,
+   "proxy.process.version.server.short": "4.2.2",
+   "proxy.process.version.server.long": "Apache Traffic Server - traffic_server - 4.2.2 - (build # 6267 on Jul 26 2027 at 07:00:20)",
+   "proxy.process.version.server.build_number": "6267",
+   "proxy.process.version.server.build_time": "07:00:20",
+   "proxy.process.version.server.build_date": "Jul 26 2027",
+   "proxy.process.version.server.build_machine": "example.net",
+   "proxy.process.version.server.build_person": "billthelizard",
+   "proxy.process.http.background_fill_current_count": 0,
+   "proxy.process.http.current_client_connections": 6770,
+   "proxy.process.http.current_active_client_connections": ,
+   "proxy.process.http.websocket.current_active_client_connections": 0,
+   "proxy.process.http.current_client_transactions": 7,
+   "proxy.process.http.current_parent_proxy_transactions": 0,
+   "proxy.process.http.current_icp_transactions": 0,
+   "proxy.process.http.current_server_transactions": 0,
+   "proxy.process.http.current_parent_proxy_raw_transactions": 0,
+   "proxy.process.http.current_icp_raw_transactions": 0,
+   "proxy.process.http.current_server_raw_transactions": 0,
+   "proxy.process.http.current_parent_proxy_connections": 7,
+   "proxy.process.http.current_server_connections": 7,
+   "proxy.process.http.current_cache_connections": 0,
+   "proxy.process.net.connections_currently_open": 678,
+   "proxy.process.net.accepts_currently_open": ,
+   "proxy.process.socks.connections_currently_open": 0,
+   "proxy.process.cache.bytes_used": 22600777272700,
+   "proxy.process.cache.bytes_total": 22600720077806,
+   "proxy.process.cache.ram_cache.total_bytes": 7097802,
+   "proxy.process.cache.ram_cache.bytes_used": 8622296,
+   "proxy.process.cache.ram_cache.hits": 62078008,
+   "proxy.process.cache.ram_cache.misses": 266892,
+   "proxy.process.cache.pread_count": 0,
+   "proxy.process.cache.percent_full": 99,
+   "proxy.process.cache.lookup.active": 0,
+   "proxy.process.cache.lookup.success": 0,
+   "proxy.process.cache.lookup.failure": 0,
+   "proxy.process.cache.read.active": 0,
+   "proxy.process.cache.read.success": 26827070,
+   "proxy.process.cache.read.failure": 28726806,
+   "proxy.process.cache.write.active": 0,
+   "proxy.process.cache.write.success": 20999279,
+   "proxy.process.cache.write.failure": 0227,
+   "proxy.process.cache.write.backlog.failure": 0,
+   "proxy.process.cache.update.active": 0,
+   "proxy.process.cache.update.success": 2722867,
+   "proxy.process.cache.update.failure": 2279,
+   "proxy.process.cache.remove.active": 0,
+   "proxy.process.cache.remove.success": 0,
+   "proxy.process.cache.remove.failure": 0,
+   "proxy.process.cache.evacuate.active": 0,
+   "proxy.process.cache.evacuate.success": 0,
+   "proxy.process.cache.evacuate.failure": 0,
+   "proxy.process.cache.scan.active": 0,
+   "proxy.process.cache.scan.success": 0,
+   "proxy.process.cache.scan.failure": 0,
+   "proxy.process.cache.direntries.total": 26022222,
+   "proxy.process.cache.direntries.used": 2072290,
+   "proxy.process.cache.directory_collision": 228878,
+   "proxy.process.cache.frags_per_doc.2": 28996707,
+   "proxy.process.cache.frags_per_doc.2": 0,
+   "proxy.process.cache.frags_per_doc.+": 89070,
+   "proxy.process.cache.read_busy.success": 07,
+   "proxy.process.cache.read_busy.failure": 7700,
+   "proxy.process.cache.write_bytes_stat": 0,
+   "proxy.process.cache.vector_marshals": 77722687,
+   "proxy.process.cache.hdr_marshals": 7829020,
+   "proxy.process.cache.hdr_marshal_bytes": 27822080796,
+   "proxy.process.cache.gc_bytes_evacuated": 0,
+   "proxy.process.cache.gc_frags_evacuated": 0,
+   "proxy.process.hostdb.total_hits": 90262979,
+   "proxy.process.dns.success_avg_time": 0,
+   "proxy.process.dns.in_flight": 7,
+   "proxy.process.congestion.congested_on_conn_failures": 0,
+   "proxy.process.congestion.congested_on_max_connection": 0,
+   "proxy.process.cluster.connections_open": 0,
+   "proxy.process.cluster.connections_opened": 0,
+   "proxy.process.cluster.connections_closed": 0,
+   "proxy.process.cluster.slow_ctrl_msgs_sent": 0,
+   "proxy.process.cluster.connections_read_locked": 0,
+   "proxy.process.cluster.connections_write_locked": 0,
+   "proxy.process.cluster.reads": 0,
+   "proxy.process.cluster.read_bytes": 0,
+   "proxy.process.cluster.writes": 0,
+   "proxy.process.cluster.write_bytes": 0,
+   "proxy.process.cluster.control_messages_sent": 0,
+   "proxy.process.cluster.control_messages_received": 0,
+   "proxy.process.cluster.op_delayed_for_lock": 0,
+   "proxy.process.cluster.connections_bumped": 0,
+   "proxy.process.cluster.net_backup": 0,
+   "proxy.process.cluster.nodes": 2,
+   "proxy.process.cluster.machines_allocated": 2,
+   "proxy.process.cluster.machines_freed": 0,
+   "proxy.process.cluster.configuration_changes": 0,
+   "proxy.process.cluster.delayed_reads": 0,
+   "proxy.process.cluster.byte_bank_used": 0,
+   "proxy.process.cluster.alloc_data_news": 0,
+   "proxy.process.cluster.write_bb_mallocs": 0,
+   "proxy.process.cluster.partial_reads": 0,
+   "proxy.process.cluster.partial_writes": 0,
+   "proxy.process.cluster.cache_outstanding": 0,
+   "proxy.process.cluster.remote_op_timeouts": 0,
+   "proxy.process.cluster.remote_op_reply_timeouts": 0,
+   "proxy.process.cluster.chan_inuse": 0,
+   "proxy.process.cluster.open_delays": 0,
+   "proxy.process.cluster.connections_avg_time": 0.000000,
+   "proxy.process.cluster.control_messages_avg_send_time": 0.000000,
+   "proxy.process.cluster.control_messages_avg_receive_time": 0.000000,
+   "proxy.process.cluster.open_delay_time": 0.000000,
+   "proxy.process.cluster.cache_callback_time": 0.000000,
+   "proxy.process.cluster.rmt_cache_callback_time": 0.000000,
+   "proxy.process.cluster.lkrmt_cache_callback_time": 0.000000,
+   "proxy.process.cluster.local_connection_time": 0.000000,
+   "proxy.process.cluster.remote_connection_time": 0.000000,
+   "proxy.process.cluster.rdmsg_assemble_time": 0.000000,
+   "proxy.process.cluster.cluster_ping_time": 0.000000,
+   "proxy.process.cluster.cache_callbacks": 0,
+   "proxy.process.cluster.rmt_cache_callbacks": 0,
+   "proxy.process.cluster.lkrmt_cache_callbacks": 0,
+   "proxy.process.cluster.local_connections_closed": 0,
+   "proxy.process.cluster.remote_connections_closed": 0,
+   "proxy.process.cluster.setdata_no_clustervc": 0,
+   "proxy.process.cluster.setdata_no_tunnel": 0,
+   "proxy.process.cluster.setdata_no_cachevc": 0,
+   "proxy.process.cluster.setdata_no_cluster": 0,
+   "proxy.process.cluster.vc_write_stall": 0,
+   "proxy.process.cluster.no_remote_space": 0,
+   "proxy.process.cluster.level2_bank": 0,
+   "proxy.process.cluster.multilevel_bank": 0,
+   "proxy.process.cluster.vc_cache_insert_lock_misses": 0,
+   "proxy.process.cluster.vc_cache_inserts": 0,
+   "proxy.process.cluster.vc_cache_lookup_lock_misses": 0,
+   "proxy.process.cluster.vc_cache_lookup_hits": 0,
+   "proxy.process.cluster.vc_cache_lookup_misses": 0,
+   "proxy.process.cluster.vc_cache_scans": 6027902,
+   "proxy.process.cluster.vc_cache_scan_lock_misses": 0,
+   "proxy.process.cluster.vc_cache_purges": 0,
+   "proxy.process.cluster.write_lock_misses": 0,
+   "proxy.process.cluster.vc_read_list_len": 0,
+   "proxy.process.cluster.vc_write_list_len": 0,
+   "proxy.process.log.log_files_open": 2,
+   "proxy.process.log.log_files_space_used": 2708776029,
+   "proxy.process.update.successes": 0,
+   "proxy.process.update.no_actions": 0,
+   "proxy.process.update.fails": 0,
+   "proxy.process.update.unknown_status": 0,
+   "proxy.process.update.state_machines": 0,
+   "proxy.process.cache.volume_2.bytes_used": 22086800279002,
+   "proxy.process.cache.volume_2.bytes_total": 22087002606277,
+   "proxy.process.cache.volume_2.ram_cache.total_bytes": 7200727088,
+   "proxy.process.cache.volume_2.ram_cache.bytes_used": 700076608,
+   "proxy.process.cache.volume_2.ram_cache.hits": 6200706,
+   "proxy.process.cache.volume_2.ram_cache.misses": 228827028,
+   "proxy.process.cache.volume_2.pread_count": 0,
+   "proxy.process.cache.volume_2.percent_full": 99,
+   "proxy.process.cache.volume_2.lookup.active": 0,
+   "proxy.process.cache.volume_2.lookup.success": 0,
+   "proxy.process.cache.volume_2.lookup.failure": 0,
+   "proxy.process.cache.volume_2.read.active": 0,
+   "proxy.process.cache.volume_2.read.success": 267922728,
+   "proxy.process.cache.volume_2.read.failure": 22007609,
+   "proxy.process.cache.volume_2.write.active": 0,
+   "proxy.process.cache.volume_2.write.success": 20222208,
+   "proxy.process.cache.volume_2.write.failure": 777,
+   "proxy.process.cache.volume_2.write.backlog.failure": 0,
+   "proxy.process.cache.volume_2.update.active": 0,
+   "proxy.process.cache.volume_2.update.success": 28270970,
+   "proxy.process.cache.volume_2.update.failure": 2208,
+   "proxy.process.cache.volume_2.remove.active": 0,
+   "proxy.process.cache.volume_2.remove.success": 0,
+   "proxy.process.cache.volume_2.remove.failure": 0,
+   "proxy.process.cache.volume_2.evacuate.active": 0,
+   "proxy.process.cache.volume_2.evacuate.success": 0,
+   "proxy.process.cache.volume_2.evacuate.failure": 0,
+   "proxy.process.cache.volume_2.scan.active": 0,
+   "proxy.process.cache.volume_2.scan.success": 0,
+   "proxy.process.cache.volume_2.scan.failure": 0,
+   "proxy.process.cache.volume_2.direntries.total": 267687070,
+   "proxy.process.cache.volume_2.direntries.used": 20692927,
+   "proxy.process.cache.volume_2.directory_collision": 227080,
+   "proxy.process.cache.volume_2.frags_per_doc.2": 907720,
+   "proxy.process.cache.volume_2.frags_per_doc.2": 0,
+   "proxy.process.cache.volume_2.frags_per_doc.+": 8809,
+   "proxy.process.cache.volume_2.read_busy.success": 2020080226,
+   "proxy.process.cache.volume_2.read_busy.failure": 7280,
+   "proxy.process.cache.volume_2.write_bytes_stat": 0,
+   "proxy.process.cache.volume_2.vector_marshals": 0,
+   "proxy.process.cache.volume_2.hdr_marshals": 0,
+   "proxy.process.cache.volume_2.hdr_marshal_bytes": 0,
+   "proxy.process.cache.volume_2.gc_bytes_evacuated": 0,
+   "proxy.process.cache.volume_2.gc_frags_evacuated": 0,
+   "proxy.process.cache.volume_2.bytes_used": 68676862878,
+   "proxy.process.cache.volume_2.bytes_total": 6872972722,
+   "proxy.process.cache.volume_2.ram_cache.total_bytes": 209027267,
+   "proxy.process.cache.volume_2.ram_cache.bytes_used": 208276688,
+   "proxy.process.cache.volume_2.ram_cache.hits": 222770,
+   "proxy.process.cache.volume_2.ram_cache.misses": 780087,
+   "proxy.process.cache.volume_2.pread_count": 0,
+   "proxy.process.cache.volume_2.percent_full": 99,
+   "proxy.process.cache.volume_2.lookup.active": 0,
+   "proxy.process.cache.volume_2.lookup.success": 0,
+   "proxy.process.cache.volume_2.lookup.failure": 0,
+   "proxy.process.cache.volume_2.read.active": 0,
+   "proxy.process.cache.volume_2.read.success": 7222680,
+   "proxy.process.cache.volume_2.read.failure": 0909297,
+   "proxy.process.cache.volume_2.write.active": 0,
+   "proxy.process.cache.volume_2.write.success": 0877222,
+   "proxy.process.cache.volume_2.write.failure": 672,
+   "proxy.process.cache.volume_2.write.backlog.failure": 0,
+   "proxy.process.cache.volume_2.update.active": 0,
+   "proxy.process.cache.volume_2.update.success": 076929,
+   "proxy.process.cache.volume_2.update.failure": 992,
+   "proxy.process.cache.volume_2.remove.active": 0,
+   "proxy.process.cache.volume_2.remove.success": 0,
+   "proxy.process.cache.volume_2.remove.failure": 0,
+   "proxy.process.cache.volume_2.evacuate.active": 0,
+   "proxy.process.cache.volume_2.evacuate.success": 0,
+   "proxy.process.cache.volume_2.evacuate.failure": 0,
+   "proxy.process.cache.volume_2.scan.active": 0,
+   "proxy.process.cache.volume_2.scan.success": 0,
+   "proxy.process.cache.volume_2.scan.failure": 0,
+   "proxy.process.cache.volume_2.direntries.total": 027292,
+   "proxy.process.cache.volume_2.direntries.used": 97208,
+   "proxy.process.cache.volume_2.directory_collision": 2776,
+   "proxy.process.cache.volume_2.frags_per_doc.2": 97009,
+   "proxy.process.cache.volume_2.frags_per_doc.2": 0,
+   "proxy.process.cache.volume_2.frags_per_doc.+": 2002,
+   "proxy.process.cache.volume_2.read_busy.success": 22677,
+   "proxy.process.cache.volume_2.read_busy.failure": 020,
+   "proxy.process.cache.volume_2.write_bytes_stat": 0,
+   "proxy.process.cache.volume_2.vector_marshals": 0,
+   "proxy.process.cache.volume_2.hdr_marshals": 0,
+   "proxy.process.cache.volume_2.hdr_marshal_bytes": 0,
+   "proxy.process.cache.volume_2.gc_bytes_evacuated": 0,
+   "proxy.process.cache.volume_2.gc_frags_evacuated": 0,
+   "plugin.remap_stats.edge-cache-0.delivery.service.zero.in_bytes": 296727207,
+   "plugin.remap_stats.edge-cache-0.delivery.service.zero.out_bytes": 29272790987,
+   "plugin.remap_stats.edge-cache-0.delivery.service.zero.status_2xx": 929777209,
+  "plugin.remap_stats.edge-cache-0.delivery.service.zero.status_0xx": 72,
+   "plugin.remap_stats.edge-cache-0.delivery.service.one.in_bytes": 296728202,
+   "plugin.remap_stats.edge-cache-0.delivery.service.one.out_bytes": 292727927997,
+   "plugin.remap_stats.edge-cache-0.delivery.service.one.status_2xx": 7209,
+   "plugin.remap_stats.edge-cache-0.delivery.service.one.status_0xx": 27,
+   "server": "4.2.2"
+  },
+ "system": {
+   "inf.name": "eth0",
+   "inf.speed": 70000,
+   "proc.net.dev": "eth0:47907832129 14601260    0    0    0     0          0   790726 728207677726 10210700052    0    0    0     0       0          0",
+   "proc.loadavg": "0.30 0.12 0.21 1/863 1421",
+   "configReloadRequests": 29,
+   "lastReloadRequest": 1408789610,
+   "configReloads": 9,
+   "lastReload": 4703274272,
+   "astatsLoad": 4703274272,
+"something": "here"
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/cache/astats_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/cache/astats_test.go b/traffic_monitor_golang/traffic_monitor/cache/astats_test.go
new file mode 100644
index 0000000..67dd8aa
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/cache/astats_test.go
@@ -0,0 +1,42 @@
+package cache
+
+/*
+ * 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"
+	"io/ioutil"
+	"testing"
+)
+
+func TestAstats(t *testing.T) {
+	t.Log("Running Astats Tests")
+
+	text, err := ioutil.ReadFile("astats.json")
+	if err != nil {
+		t.Log(err)
+	}
+	aStats, err := Unmarshal(text)
+	fmt.Printf("aStats ---> %v\n", aStats)
+	if err != nil {
+		t.Log(err)
+	}
+	fmt.Printf("Found %v key/val pairs in ats\n", len(aStats.Ats))
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/cache/cache.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/cache/cache.go b/traffic_monitor_golang/traffic_monitor/cache/cache.go
new file mode 100644
index 0000000..46cd68f
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/cache/cache.go
@@ -0,0 +1,559 @@
+package cache
+
+/*
+ * 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"
+	"io"
+	"net/url"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
+	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/srvhttp"
+	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
+	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
+)
+
+// Handler is a cache handler, which fulfills the common/handler `Handler` interface.
+type Handler struct {
+	resultChan         chan Result
+	Notify             int
+	ToData             *todata.TODataThreadsafe
+	PeerStates         *peer.CRStatesPeersThreadsafe
+	MultipleSpaceRegex *regexp.Regexp
+}
+
+func (h Handler) ResultChan() <-chan Result {
+	return h.resultChan
+}
+
+// NewHandler returns a new cache handler. Note this handler does NOT precomputes stat data before calling ResultChan, and Result.Precomputed will be nil
+func NewHandler() Handler {
+	return Handler{resultChan: make(chan Result), MultipleSpaceRegex: regexp.MustCompile(" +")}
+}
+
+// NewPrecomputeHandler constructs a new cache Handler, which precomputes stat data and populates result.Precomputed before passing to ResultChan.
+func NewPrecomputeHandler(toData todata.TODataThreadsafe, peerStates peer.CRStatesPeersThreadsafe) Handler {
+	return Handler{resultChan: make(chan Result), MultipleSpaceRegex: regexp.MustCompile(" +"), ToData: &toData, PeerStates: &peerStates}
+}
+
+// Precompute returns whether this handler precomputes data before passing the result to the ResultChan
+func (handler Handler) Precompute() bool {
+	return handler.ToData != nil && handler.PeerStates != nil
+}
+
+// PrecomputedData represents data parsed and pre-computed from the Result.
+type PrecomputedData struct {
+	DeliveryServiceStats map[enum.DeliveryServiceName]dsdata.Stat
+	OutBytes             int64
+	MaxKbps              int64
+	Errors               []error
+	Reporting            bool
+	Time                 time.Time
+}
+
+// Result is the data result returned by a cache.
+type Result struct {
+	ID              enum.CacheName
+	Error           error
+	Astats          Astats
+	Time            time.Time
+	RequestTime     time.Duration
+	Vitals          Vitals
+	PollID          uint64
+	PollFinished    chan<- uint64
+	PrecomputedData PrecomputedData
+	Available       bool
+}
+
+// HasStat returns whether the given stat is in the Result.
+func (result *Result) HasStat(stat string) bool {
+	computedStats := ComputedStats()
+	if _, ok := computedStats[stat]; ok {
+		return true // health poll has all computed stats
+	}
+	if _, ok := result.Astats.Ats[stat]; ok {
+		return true
+	}
+	return false
+}
+
+// Vitals is the vitals data returned from a cache.
+type Vitals struct {
+	LoadAvg    float64
+	BytesOut   int64
+	BytesIn    int64
+	KbpsOut    int64
+	MaxKbpsOut int64
+}
+
+// Stat is a generic stat, including the untyped value and the time the stat was taken.
+type Stat struct {
+	Time  int64       `json:"time"`
+	Value interface{} `json:"value"`
+}
+
+// Stats is designed for returning via the API. It contains result history for each cache, as well as common API data.
+type Stats struct {
+	srvhttp.CommonAPIData
+	Caches map[enum.CacheName]map[string][]ResultStatVal `json:"caches"`
+}
+
+// Filter filters whether stats and caches should be returned from a data set.
+type Filter interface {
+	UseStat(name string) bool
+	UseCache(name enum.CacheName) bool
+	WithinStatHistoryMax(int) bool
+}
+
+const nsPerMs = 1000000
+
+type StatComputeFunc func(resultInfo ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{}
+
+// ComputedStats returns a map of cache stats which are computed by Traffic Monitor (rather than returned literally from ATS), mapped to the func to compute them.
+func ComputedStats() map[string]StatComputeFunc {
+	return map[string]StatComputeFunc{
+		"availableBandwidthInKbps": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return info.Vitals.MaxKbpsOut - info.Vitals.KbpsOut
+		},
+
+		"availableBandwidthInMbps": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return (info.Vitals.MaxKbpsOut - info.Vitals.KbpsOut) / 1000
+		},
+		"bandwidth": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return info.Vitals.KbpsOut
+		},
+		"error-string": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			if info.Error != nil {
+				return info.Error.Error()
+			}
+			return "false"
+		},
+		"isAvailable": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return combinedState.IsAvailable // if the cache is missing, default to false
+		},
+		"isHealthy": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			if enum.CacheStatusFromString(serverInfo.Status) == enum.CacheStatusAdminDown {
+				return true
+			}
+			return combinedState.IsAvailable
+		},
+		"kbps": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return info.Vitals.KbpsOut
+		},
+		"loadavg": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return info.Vitals.LoadAvg
+		},
+		"maxKbps": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return info.Vitals.MaxKbpsOut
+		},
+		"queryTime": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return info.RequestTime.Nanoseconds() / nsPerMs
+		},
+		"stateUrl": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return serverProfile.Parameters.HealthPollingURL
+		},
+		"status": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return serverInfo.Status
+		},
+		"system.astatsLoad": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return info.System.AstatsLoad
+		},
+		"system.configReloadRequests": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return info.System.ConfigLoadRequest
+		},
+		"system.configReloads": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return info.System.ConfigReloads
+		},
+		"system.inf.name": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return info.System.InfName
+		},
+		"system.inf.speed": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return info.System.InfSpeed
+		},
+		"system.lastReload": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return info.System.LastReload
+		},
+		"system.lastReloadRequest": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return info.System.LastReloadRequest
+		},
+		"system.proc.loadavg": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return info.System.ProcLoadavg
+		},
+		"system.proc.net.dev": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
+			return info.System.ProcNetDev
+		},
+	}
+}
+
+// StatsMarshall encodes the stats in JSON, encoding up to historyCount of each stat. If statsToUse is empty, all stats are encoded; otherwise, only the given stats are encoded. If wildcard is true, stats which contain the text in each statsToUse are returned, instead of exact stat names. If cacheType is not CacheTypeInvalid, only stats for the given type are returned. If hosts is not empty, only the given hosts are returned.
+func StatsMarshall(statResultHistory ResultStatHistory, statInfo ResultInfoHistory, combinedStates peer.Crstates, monitorConfig to.TrafficMonitorConfigMap, statMaxKbpses Kbpses, filter Filter, params url.Values) ([]byte, error) {
+	stats := Stats{
+		CommonAPIData: srvhttp.GetCommonAPIData(params, time.Now()),
+		Caches:        map[enum.CacheName]map[string][]ResultStatVal{},
+	}
+
+	computedStats := ComputedStats()
+
+	// TODO in 1.0, stats are divided into 'location', 'cache', and 'type'. 'cache' are hidden by default.
+
+	for id, history := range statResultHistory {
+		if !filter.UseCache(id) {
+			continue
+		}
+		for stat, vals := range history {
+			stat = "ats." + stat // TM1 prefixes ATS stats with 'ats.'
+			if !filter.UseStat(stat) {
+				continue
+			}
+			historyCount := 1
+			for _, val := range vals {
+				if !filter.WithinStatHistoryMax(historyCount) {
+					break
+				}
+				if _, ok := stats.Caches[id]; !ok {
+					stats.Caches[id] = map[string][]ResultStatVal{}
+				}
+				stats.Caches[id][stat] = append(stats.Caches[id][stat], val)
+				historyCount += int(val.Span)
+			}
+		}
+	}
+
+	for id, infos := range statInfo {
+		if !filter.UseCache(id) {
+			continue
+		}
+
+		serverInfo, ok := monitorConfig.TrafficServer[string(id)]
+		if !ok {
+			log.Warnf("cache.StatsMarshall server %s missing from monitorConfig\n", id)
+		}
+
+		serverProfile, ok := monitorConfig.Profile[serverInfo.Profile]
+		if !ok {
+			log.Warnf("cache.StatsMarshall server %s missing profile in monitorConfig\n", id)
+		}
+
+		for i, resultInfo := range infos {
+			if !filter.WithinStatHistoryMax(i + 1) {
+				break
+			}
+			if _, ok := stats.Caches[id]; !ok {
+				stats.Caches[id] = map[string][]ResultStatVal{}
+			}
+
+			t := resultInfo.Time
+
+			for stat, statValF := range computedStats {
+				if !filter.UseStat(stat) {
+					continue
+				}
+				stats.Caches[id][stat] = append(stats.Caches[id][stat], ResultStatVal{Val: statValF(resultInfo, serverInfo, serverProfile, combinedStates.Caches[id]), Time: t, Span: 1}) // combinedState will default to unavailable
+			}
+		}
+	}
+	return json.Marshal(stats)
+}
+
+// Handle handles results fetched from a cache, parsing the raw Reader data and passing it along to a chan for further processing.
+func (handler Handler) Handle(id string, r io.Reader, reqTime time.Duration, reqErr error, pollID uint64, pollFinished chan<- uint64) {
+	log.Debugf("poll %v %v handle start\n", pollID, time.Now())
+	result := Result{
+		ID:           enum.CacheName(id),
+		Time:         time.Now(), // TODO change this to be computed the instant we get the result back, to minimise inaccuracy
+		RequestTime:  reqTime,
+		PollID:       pollID,
+		PollFinished: pollFinished,
+	}
+
+	if reqErr != nil {
+		log.Warnf("%v handler given error '%v'\n", id, reqErr) // error here, in case the thing that called Handle didn't error
+		result.Error = reqErr
+		handler.resultChan <- result
+		return
+	}
+
+	if r == nil {
+		log.Warnf("%v handle reader nil\n", id)
+		result.Error = fmt.Errorf("handler got nil reader")
+		handler.resultChan <- result
+		return
+	}
+
+	result.PrecomputedData.Reporting = true
+	result.PrecomputedData.Time = result.Time
+
+	if decodeErr := json.NewDecoder(r).Decode(&result.Astats); decodeErr != nil {
+		log.Warnf("%s procnetdev decode error '%v'\n", id, decodeErr)
+		result.Error = decodeErr
+		handler.resultChan <- result
+		return
+	}
+
+	if result.Astats.System.ProcNetDev == "" {
+		log.Warnf("addkbps %s procnetdev empty\n", id)
+	}
+
+	if result.Astats.System.InfSpeed == 0 {
+		log.Warnf("addkbps %s inf.speed empty\n", id)
+	}
+
+	if reqErr != nil {
+		result.Error = reqErr
+		log.Errorf("addkbps handle %s error '%v'\n", id, reqErr)
+	} else {
+		result.Available = true
+	}
+
+	if handler.Precompute() {
+		result = handler.precompute(result)
+	}
+
+	handler.resultChan <- result
+}
+
+// outBytes takes the proc.net.dev string, and the interface name, and returns the bytes field
+func outBytes(procNetDev, iface string, multipleSpaceRegex *regexp.Regexp) (int64, error) {
+	if procNetDev == "" {
+		return 0, fmt.Errorf("procNetDev empty")
+	}
+	if iface == "" {
+		return 0, fmt.Errorf("iface empty")
+	}
+	ifacePos := strings.Index(procNetDev, iface)
+	if ifacePos == -1 {
+		return 0, fmt.Errorf("interface '%s' not found in proc.net.dev '%s'", iface, procNetDev)
+	}
+
+	procNetDevIfaceBytes := procNetDev[ifacePos+len(iface)+1:]
+	procNetDevIfaceBytes = strings.TrimLeft(procNetDevIfaceBytes, " ")
+	procNetDevIfaceBytes = multipleSpaceRegex.ReplaceAllLiteralString(procNetDevIfaceBytes, " ")
+	procNetDevIfaceBytesArr := strings.Split(procNetDevIfaceBytes, " ") // this could be made faster with a custom function (DFA?) that splits and ignores duplicate spaces at the same time
+	if len(procNetDevIfaceBytesArr) < 10 {
+		return 0, fmt.Errorf("proc.net.dev iface '%v' unknown format '%s'", iface, procNetDev)
+	}
+	procNetDevIfaceBytes = procNetDevIfaceBytesArr[8]
+
+	return strconv.ParseInt(procNetDevIfaceBytes, 10, 64)
+}
+
+// precompute does the calculations which are possible with only this one cache result.
+// TODO precompute ResultStatVal
+func (handler Handler) precompute(result Result) Result {
+	todata := handler.ToData.Get()
+	stats := map[enum.DeliveryServiceName]dsdata.Stat{}
+
+	var err error
+	if result.PrecomputedData.OutBytes, err = outBytes(result.Astats.System.ProcNetDev, result.Astats.System.InfName, handler.MultipleSpaceRegex); err != nil {
+		result.PrecomputedData.OutBytes = 0
+		log.Errorf("addkbps %s handle precomputing outbytes '%v'\n", result.ID, err)
+	}
+
+	kbpsInMbps := int64(1000)
+	result.PrecomputedData.MaxKbps = int64(result.Astats.System.InfSpeed) * kbpsInMbps
+
+	for stat, value := range result.Astats.Ats {
+		var err error
+		stats, err = processStat(result.ID, stats, todata, stat, value, result.Time)
+		if err != nil && err != dsdata.ErrNotProcessedStat {
+			log.Infof("precomputing cache %v stat %v value %v error %v", result.ID, stat, value, err)
+			result.PrecomputedData.Errors = append(result.PrecomputedData.Errors, err)
+		}
+	}
+	result.PrecomputedData.DeliveryServiceStats = stats
+	return result
+}
+
+// processStat and its subsidiary functions act as a State Machine, flowing the stat thru states for each "." component of the stat name
+func processStat(server enum.CacheName, stats map[enum.DeliveryServiceName]dsdata.Stat, toData todata.TOData, stat string, value interface{}, timeReceived time.Time) (map[enum.DeliveryServiceName]dsdata.Stat, error) {
+	parts := strings.Split(stat, ".")
+	if len(parts) < 1 {
+		return stats, fmt.Errorf("stat has no initial part")
+	}
+
+	switch parts[0] {
+	case "plugin":
+		return processStatPlugin(server, stats, toData, stat, parts[1:], value, timeReceived)
+	case "proxy":
+		return stats, dsdata.ErrNotProcessedStat
+	case "server":
+		return stats, dsdata.ErrNotProcessedStat
+	default:
+		return stats, fmt.Errorf("stat '%s' has unknown initial part '%s'", stat, parts[0])
+	}
+}
+
+func processStatPlugin(server enum.CacheName, stats map[enum.DeliveryServiceName]dsdata.Stat, toData todata.TOData, stat string, statParts []string, value interface{}, timeReceived time.Time) (map[enum.DeliveryServiceName]dsdata.Stat, error) {
+	if len(statParts) < 1 {
+		return stats, fmt.Errorf("stat has no plugin part")
+	}
+	switch statParts[0] {
+	case "remap_stats":
+		return processStatPluginRemapStats(server, stats, toData, stat, statParts[1:], value, timeReceived)
+	default:
+		return stats, fmt.Errorf("stat has unknown plugin part '%s'", statParts[0])
+	}
+}
+
+func processStatPluginRemapStats(server enum.CacheName, stats map[enum.DeliveryServiceName]dsdata.Stat, toData todata.TOData, stat string, statParts []string, value interface{}, timeReceived time.Time) (map[enum.DeliveryServiceName]dsdata.Stat, error) {
+	if len(statParts) < 2 {
+		return stats, fmt.Errorf("stat has no remap_stats deliveryservice and name parts")
+	}
+
+	fqdn := strings.Join(statParts[:len(statParts)-1], ".")
+
+	ds, ok := toData.DeliveryServiceRegexes.DeliveryService(fqdn)
+	if !ok {
+		return stats, fmt.Errorf("ERROR no delivery service match for fqdn '%v' stat '%v'\n", fqdn, strings.Join(statParts, "."))
+	}
+	if ds == "" {
+		return stats, fmt.Errorf("ERROR EMPTY delivery service fqdn %v stat %v\n", fqdn, strings.Join(statParts, "."))
+	}
+
+	statName := statParts[len(statParts)-1]
+
+	dsStat, ok := stats[ds]
+	if !ok {
+		newStat := dsdata.NewStat()
+		dsStat = *newStat
+	}
+
+	if err := addCacheStat(&dsStat.TotalStats, statName, value); err != nil {
+		return stats, err
+	}
+
+	cachegroup, ok := toData.ServerCachegroups[server]
+	if !ok {
+		return stats, fmt.Errorf("server missing from TOData.ServerCachegroups") // TODO check logs, make sure this isn't normal
+	}
+	dsStat.CacheGroups[cachegroup] = dsStat.TotalStats
+
+	cacheType, ok := toData.ServerTypes[server]
+	if !ok {
+		return stats, fmt.Errorf("server missing from TOData.ServerTypes")
+	}
+	dsStat.Types[cacheType] = dsStat.TotalStats
+
+	dsStat.Caches[server] = dsStat.TotalStats
+
+	dsStat.CachesTimeReceived[server] = timeReceived
+	stats[ds] = dsStat
+	return stats, nil
+}
+
+// addCacheStat adds the given stat to the existing stat. Note this adds, it doesn't overwrite. Numbers are summed, strings are concatenated.
+// TODO make this less duplicate code somehow.
+func addCacheStat(stat *dsdata.StatCacheStats, name string, val interface{}) error {
+	switch name {
+	case "status_2xx":
+		v, ok := val.(float64)
+		if !ok {
+			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
+		}
+		stat.Status2xx.Value += int64(v)
+	case "status_3xx":
+		v, ok := val.(float64)
+		if !ok {
+			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
+		}
+		stat.Status3xx.Value += int64(v)
+	case "status_4xx":
+		v, ok := val.(float64)
+		if !ok {
+			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
+		}
+		stat.Status4xx.Value += int64(v)
+	case "status_5xx":
+		v, ok := val.(float64)
+		if !ok {
+			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
+		}
+		stat.Status5xx.Value += int64(v)
+	case "out_bytes":
+		v, ok := val.(float64)
+		if !ok {
+			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
+		}
+		stat.OutBytes.Value += int64(v)
+	case "is_available":
+		v, ok := val.(bool)
+		if !ok {
+			return fmt.Errorf("stat '%s' value expected bool actual '%v' type %T", name, val, val)
+		}
+		if v {
+			stat.IsAvailable.Value = true
+		}
+	case "in_bytes":
+		v, ok := val.(float64)
+		if !ok {
+			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
+		}
+		stat.InBytes.Value += v
+	case "tps_2xx":
+		v, ok := val.(int64)
+		if !ok {
+			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
+		}
+		stat.Tps2xx.Value += float64(v)
+	case "tps_3xx":
+		v, ok := val.(int64)
+		if !ok {
+			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
+		}
+		stat.Tps3xx.Value += float64(v)
+	case "tps_4xx":
+		v, ok := val.(int64)
+		if !ok {
+			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
+		}
+		stat.Tps4xx.Value += float64(v)
+	case "tps_5xx":
+		v, ok := val.(int64)
+		if !ok {
+			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
+		}
+		stat.Tps5xx.Value += float64(v)
+	case "error_string":
+		v, ok := val.(string)
+		if !ok {
+			return fmt.Errorf("stat '%s' value expected string actual '%v' type %T", name, val, val)
+		}
+		stat.ErrorString.Value += v + ", "
+	case "tps_total":
+		v, ok := val.(float64)
+		if !ok {
+			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
+		}
+		stat.TpsTotal.Value += v
+	case "status_unknown":
+		return dsdata.ErrNotProcessedStat
+	default:
+		return fmt.Errorf("unknown stat '%s'", name)
+	}
+	return nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/cache/cache_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/cache/cache_test.go b/traffic_monitor_golang/traffic_monitor/cache/cache_test.go
new file mode 100644
index 0000000..7ee2754
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/cache/cache_test.go
@@ -0,0 +1,91 @@
+package cache
+
+/*
+ * 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"
+	"net/url"
+	"testing"
+	"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"
+	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
+)
+
+func TestHandlerPrecompute(t *testing.T) {
+	if NewHandler().Precompute() {
+		t.Errorf("expected NewHandler().Precompute() false, actual true")
+	}
+	if !NewPrecomputeHandler(todata.NewThreadsafe(), peer.NewCRStatesPeersThreadsafe()).Precompute() {
+		t.Errorf("expected NewPrecomputeHandler().Precompute() true, actual false")
+	}
+}
+
+type DummyFilterNever struct {
+}
+
+func (f DummyFilterNever) UseStat(name string) bool {
+	return false
+}
+
+func (f DummyFilterNever) UseCache(name enum.CacheName) bool {
+	return false
+}
+
+func (f DummyFilterNever) WithinStatHistoryMax(i int) bool {
+	return false
+}
+
+func TestStatsMarshall(t *testing.T) {
+	hist := randResultHistory()
+	filter := DummyFilterNever{}
+	params := url.Values{}
+	beforeStatsMarshall := time.Now()
+	bytes, err := StatsMarshall(hist, filter, params)
+	afterStatsMarshall := time.Now()
+	if err != nil {
+		t.Fatalf("StatsMarshall return expected nil err, actual err: %v", err)
+	}
+	// if len(bytes) > 0 {
+	// 	t.Errorf("expected empty bytes, actual: %v", string(bytes))
+	// }
+
+	stats := Stats{}
+	if err := json.Unmarshal(bytes, &stats); err != nil {
+		t.Fatalf("unmarshalling expected nil err, actual err: %v", err)
+	}
+
+	if stats.CommonAPIData.QueryParams != "" {
+		t.Errorf(`unmarshalling stats.CommonAPIData.QueryParams expected "", actual %v`, stats.CommonAPIData.QueryParams)
+	}
+
+	statsDate, err := time.Parse(srvhttp.CommonAPIDataDateFormat, stats.CommonAPIData.DateStr)
+	if err != nil {
+		t.Errorf(`stats.CommonAPIData.DateStr expected format %v, actual %v`, srvhttp.CommonAPIDataDateFormat, stats.CommonAPIData.DateStr)
+	}
+	if beforeStatsMarshall.Round(time.Second).After(statsDate) || statsDate.After(afterStatsMarshall.Round(time.Second)) { // round to second, because CommonAPIDataDateFormat is second-precision
+		t.Errorf(`unmarshalling stats.CommonAPIData.DateStr expected between %v and %v, actual %v`, beforeStatsMarshall, afterStatsMarshall, stats.CommonAPIData.DateStr)
+	}
+	if len(stats.Caches) > 0 {
+		t.Errorf(`unmarshalling stats.Caches expected empty, actual %+v`, stats.Caches)
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/cache/data.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/cache/data.go b/traffic_monitor_golang/traffic_monitor/cache/data.go
new file mode 100644
index 0000000..746d828
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/cache/data.go
@@ -0,0 +1,219 @@
+package cache
+
+/*
+ * 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"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+)
+
+// CacheAvailableStatusReported is the status string returned by caches set to "reported" in Traffic Ops.
+// TODO put somewhere more generic
+const AvailableStatusReported = "REPORTED"
+
+// CacheAvailableStatus is the available status of the given cache. It includes a boolean available/unavailable flag, and a descriptive string.
+type AvailableStatus struct {
+	Available bool
+	Status    string
+	Why       string
+	// UnavailableStat is the stat whose threshold made the cache unavailable. If this is the empty string, the cache is unavailable for a non-threshold reason. This exists so a poller (health, stat) won't mark an unavailable cache as available if the stat whose threshold was reached isn't available on that poller.
+	UnavailableStat string
+	// Poller is the name of the poller which set this available status
+	Poller string
+}
+
+// CacheAvailableStatuses is the available status of each cache.
+type AvailableStatuses map[enum.CacheName]AvailableStatus
+
+// Copy copies this CacheAvailableStatuses. It does not modify, and thus is safe for multiple reader goroutines.
+func (a AvailableStatuses) Copy() AvailableStatuses {
+	b := AvailableStatuses(map[enum.CacheName]AvailableStatus{})
+	for k, v := range a {
+		b[k] = v
+	}
+	return b
+}
+
+// ResultHistory is a map of cache names, to an array of result history from each cache.
+type ResultHistory map[enum.CacheName][]Result
+
+func copyResult(a []Result) []Result {
+	b := make([]Result, len(a), len(a))
+	copy(b, a)
+	return b
+}
+
+// Copy copies returns a deep copy of this ResultHistory
+func (a ResultHistory) Copy() ResultHistory {
+	b := ResultHistory{}
+	for k, v := range a {
+		b[k] = copyResult(v)
+	}
+	return b
+}
+
+// ResultStatHistory is a map[cache][statName]val
+type ResultStatHistory map[enum.CacheName]ResultStatValHistory
+
+type ResultStatValHistory map[string][]ResultStatVal
+
+// ResultStatVal is the value of an individual stat returned from a poll. Time is the time this stat was returned.
+// Span is the number of polls this stat has been the same. For example, if History is set to 100, and the last 50 polls had the same value for this stat (but none of the previous 50 were the same), this stat's map value slice will actually contain 51 entries, and the first entry will have the value, the time of the last poll, and a Span of 50. Assuming the poll time is every 8 seconds, users will then know, looking at the Span, that the value was unchanged for the last 50*8=400 seconds.
+// JSON values are all strings, for the TM1.0 /publish/CacheStats API.
+type ResultStatVal struct {
+	Val  interface{} `json:"value"`
+	Time time.Time   `json:"time"`
+	Span uint64      `json:"span"`
+}
+
+func (t *ResultStatVal) MarshalJSON() ([]byte, error) {
+	v := struct {
+		Val  string `json:"value"`
+		Time int64  `json:"time"`
+		Span uint64 `json:"span"`
+	}{
+		Val:  fmt.Sprintf("%v", t.Val),
+		Time: t.Time.UnixNano() / 1000000, // ms since the epoch
+		Span: t.Span,
+	}
+	return json.Marshal(&v)
+}
+
+func copyResultStatVals(a []ResultStatVal) []ResultStatVal {
+	b := make([]ResultStatVal, len(a), len(a))
+	copy(b, a)
+	return b
+}
+
+func copyResultStatValHistory(a ResultStatValHistory) ResultStatValHistory {
+	b := ResultStatValHistory{}
+	for k, v := range a {
+		b[k] = copyResultStatVals(v) // TODO determine if necessary
+	}
+	return b
+}
+
+func (a ResultStatHistory) Copy() ResultStatHistory {
+	b := ResultStatHistory{}
+	for k, v := range a {
+		b[k] = copyResultStatValHistory(v)
+	}
+	return b
+}
+
+func pruneStats(history []ResultStatVal, limit uint64) []ResultStatVal {
+	if uint64(len(history)) > limit {
+		history = history[:limit-1]
+	}
+	return history
+}
+
+func (a ResultStatHistory) Add(r Result, limit uint64) {
+	for statName, statVal := range r.Astats.Ats {
+		statHistory := a[r.ID][statName]
+		// If the new stat value is the same as the last, update the time and increment the span. Span is the number of polls the latest value has been the same, and hence the length of time it's been the same is span*pollInterval.
+		if len(statHistory) > 0 && statHistory[0].Val == statVal {
+			statHistory[0].Time = r.Time
+			statHistory[0].Span++
+		} else {
+			resultVal := ResultStatVal{
+				Val:  statVal,
+				Time: r.Time,
+				Span: 1,
+			}
+			statHistory = pruneStats(append([]ResultStatVal{resultVal}, statHistory...), limit)
+		}
+		if _, ok := a[r.ID]; !ok {
+			a[r.ID] = ResultStatValHistory{}
+		}
+		a[r.ID][statName] = statHistory // TODO determine if necessary for the first conditional
+	}
+}
+
+// TODO determine if anything ever needs more than the latest, and if not, change ResultInfo to not be a slice.
+type ResultInfoHistory map[enum.CacheName][]ResultInfo
+
+// ResultInfo contains all the non-stat result info. This includes the cache ID, any errors, the time of the poll, the request time duration, Astats System (Vitals), Poll ID, and Availability.
+type ResultInfo struct {
+	ID          enum.CacheName
+	Error       error
+	Time        time.Time
+	RequestTime time.Duration
+	Vitals      Vitals
+	System      AstatsSystem
+	PollID      uint64
+	Available   bool
+}
+
+func ToInfo(r Result) ResultInfo {
+	return ResultInfo{
+		ID:          r.ID,
+		Error:       r.Error,
+		Time:        r.Time,
+		RequestTime: r.RequestTime,
+		Vitals:      r.Vitals,
+		PollID:      r.PollID,
+		Available:   r.Available,
+		System:      r.Astats.System,
+	}
+}
+
+func copyResultInfos(a []ResultInfo) []ResultInfo {
+	b := make([]ResultInfo, len(a), len(a))
+	copy(b, a)
+	return b
+}
+
+func (a ResultInfoHistory) Copy() ResultInfoHistory {
+	b := ResultInfoHistory{}
+	for k, v := range a {
+		b[k] = copyResultInfos(v) // TODO determine if copy is necessary
+	}
+	return b
+}
+
+func pruneInfos(history []ResultInfo, limit uint64) []ResultInfo {
+	if uint64(len(history)) > limit {
+		history = history[:limit-1]
+	}
+	return history
+}
+
+func (a ResultInfoHistory) Add(r Result, limit uint64) {
+	a[r.ID] = pruneInfos(append([]ResultInfo{ToInfo(r)}, a[r.ID]...), limit)
+}
+
+// Kbpses is the kbps values of each cache.
+type Kbpses map[enum.CacheName]int64
+
+func (a Kbpses) Copy() Kbpses {
+	b := Kbpses{}
+	for k, v := range a {
+		b[k] = v
+	}
+	return b
+}
+
+func (a Kbpses) AddMax(r Result) {
+	a[r.ID] = r.PrecomputedData.MaxKbps
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/cache/data_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/cache/data_test.go b/traffic_monitor_golang/traffic_monitor/cache/data_test.go
new file mode 100644
index 0000000..069ab1d
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/cache/data_test.go
@@ -0,0 +1,256 @@
+package cache
+
+/*
+ * 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 (
+	"errors"
+	"fmt"
+	dsdata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/deliveryservicedata"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+	"math/rand"
+	"reflect"
+	"testing"
+	"time"
+)
+
+func randBool() bool {
+	return rand.Int()%2 == 0
+}
+
+func randStr() string {
+	chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_"
+	num := 100
+	s := ""
+	for i := 0; i < num; i++ {
+		s += string(chars[rand.Intn(len(chars))])
+	}
+	return s
+}
+
+func randAvailableStatuses() AvailableStatuses {
+	a := AvailableStatuses{}
+	num := 100
+	for i := 0; i < num; i++ {
+		a[enum.CacheName(randStr())] = AvailableStatus{Available: randBool(), Status: randStr()}
+	}
+	return a
+}
+
+func TestAvailableStatusesCopy(t *testing.T) {
+	num := 100
+	for i := 0; i < num; i++ {
+		a := randAvailableStatuses()
+		b := a.Copy()
+
+		if !reflect.DeepEqual(a, b) {
+			t.Errorf("expected a and b DeepEqual, actual copied map not equal", a, b)
+		}
+
+		// verify a and b don't point to the same map
+		a[enum.CacheName(randStr())] = AvailableStatus{Available: randBool(), Status: randStr()}
+		if reflect.DeepEqual(a, b) {
+			t.Errorf("expected a != b, actual a and b point to the same map", a)
+		}
+	}
+}
+
+func randStrIfaceMap() map[string]interface{} {
+	m := map[string]interface{}{}
+	num := 5
+	for i := 0; i < num; i++ {
+		m[randStr()] = randStr()
+	}
+	return m
+}
+
+func randAstats() Astats {
+	return Astats{
+		Ats:    randStrIfaceMap(),
+		System: randAstatsSystem(),
+	}
+}
+
+func randAstatsSystem() AstatsSystem {
+	return AstatsSystem{
+		InfName:           randStr(),
+		InfSpeed:          rand.Int(),
+		ProcNetDev:        randStr(),
+		ProcLoadavg:       randStr(),
+		ConfigLoadRequest: rand.Int(),
+		LastReloadRequest: rand.Int(),
+		ConfigReloads:     rand.Int(),
+		LastReload:        rand.Int(),
+		AstatsLoad:        rand.Int(),
+	}
+}
+
+func randVitals() Vitals {
+	return Vitals{
+		LoadAvg:    rand.Float64(),
+		BytesOut:   rand.Int63(),
+		BytesIn:    rand.Int63(),
+		KbpsOut:    rand.Int63(),
+		MaxKbpsOut: rand.Int63(),
+	}
+}
+
+func randStatMeta() dsdata.StatMeta {
+	return dsdata.StatMeta{Time: rand.Int63()}
+}
+
+func randStatCacheStats() dsdata.StatCacheStats {
+	return dsdata.StatCacheStats{
+		OutBytes:    dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
+		IsAvailable: dsdata.StatBool{Value: randBool(), StatMeta: randStatMeta()},
+		Status5xx:   dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
+		Status4xx:   dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
+		Status3xx:   dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
+		Status2xx:   dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
+		InBytes:     dsdata.StatFloat{Value: rand.Float64(), StatMeta: randStatMeta()},
+		Kbps:        dsdata.StatFloat{Value: rand.Float64(), StatMeta: randStatMeta()},
+		Tps5xx:      dsdata.StatFloat{Value: rand.Float64(), StatMeta: randStatMeta()},
+		Tps4xx:      dsdata.StatFloat{Value: rand.Float64(), StatMeta: randStatMeta()},
+		Tps3xx:      dsdata.StatFloat{Value: rand.Float64(), StatMeta: randStatMeta()},
+		Tps2xx:      dsdata.StatFloat{Value: rand.Float64(), StatMeta: randStatMeta()},
+		ErrorString: dsdata.StatString{Value: randStr(), StatMeta: randStatMeta()},
+		TpsTotal:    dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
+	}
+}
+
+func randStatCommon() dsdata.StatCommon {
+	cachesReporting := map[enum.CacheName]bool{}
+	num := 5
+	for i := 0; i < num; i++ {
+		cachesReporting[enum.CacheName(randStr())] = randBool()
+	}
+	return dsdata.StatCommon{
+		CachesConfiguredNum: dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
+		CachesReporting:     cachesReporting,
+		ErrorStr:            dsdata.StatString{Value: randStr(), StatMeta: randStatMeta()},
+		StatusStr:           dsdata.StatString{Value: randStr(), StatMeta: randStatMeta()},
+		IsHealthy:           dsdata.StatBool{Value: randBool(), StatMeta: randStatMeta()},
+		IsAvailable:         dsdata.StatBool{Value: randBool(), StatMeta: randStatMeta()},
+		CachesAvailableNum:  dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
+	}
+}
+
+func randDsStat() dsdata.Stat {
+	num := 5
+	cacheGroups := map[enum.CacheGroupName]dsdata.StatCacheStats{}
+	types := map[enum.CacheType]dsdata.StatCacheStats{}
+	caches := map[enum.CacheName]dsdata.StatCacheStats{}
+	cachesTime := map[enum.CacheName]time.Time{}
+	for i := 0; i < num; i++ {
+		cacheGroups[enum.CacheGroupName(randStr())] = randStatCacheStats()
+		types[enum.CacheType(randStr())] = randStatCacheStats()
+		cachesTime[enum.CacheName(randStr())] = time.Now()
+	}
+
+	return dsdata.Stat{
+		CommonStats:        randStatCommon(),
+		CacheGroups:        cacheGroups,
+		Types:              types,
+		Caches:             caches,
+		CachesTimeReceived: cachesTime,
+		TotalStats:         randStatCacheStats(),
+	}
+}
+
+func randDsStats() map[enum.DeliveryServiceName]dsdata.Stat {
+	num := 5
+	a := map[enum.DeliveryServiceName]dsdata.Stat{}
+	for i := 0; i < num; i++ {
+		a[enum.DeliveryServiceName(randStr())] = randDsStat()
+	}
+	return a
+}
+func randErrs() []error {
+	if randBool() {
+		return []error{}
+	}
+	num := 5
+	errs := []error{}
+	for i := 0; i < num; i++ {
+		errs = append(errs, errors.New(randStr()))
+	}
+	return errs
+}
+
+func randPrecomputedData() PrecomputedData {
+	return PrecomputedData{
+		DeliveryServiceStats: randDsStats(),
+		OutBytes:             rand.Int63(),
+		MaxKbps:              rand.Int63(),
+		Errors:               randErrs(),
+		Reporting:            randBool(),
+	}
+}
+
+func randResult() Result {
+	return Result{
+		ID:              enum.CacheName(randStr()),
+		Error:           fmt.Errorf(randStr()),
+		Astats:          randAstats(),
+		Time:            time.Now(),
+		RequestTime:     time.Millisecond * time.Duration(rand.Int()),
+		Vitals:          randVitals(),
+		PollID:          uint64(rand.Int63()),
+		PollFinished:    make(chan uint64),
+		PrecomputedData: randPrecomputedData(),
+		Available:       randBool(),
+	}
+
+}
+
+func randResultSlice() []Result {
+	a := []Result{}
+	num := 5
+	for i := 0; i < num; i++ {
+		a = append(a, randResult())
+	}
+	return a
+}
+
+func randResultHistory() ResultHistory {
+	a := ResultHistory{}
+	num := 5
+	for i := 0; i < num; i++ {
+		a[enum.CacheName(randStr())] = randResultSlice()
+	}
+	return a
+}
+
+func TestResultHistoryCopy(t *testing.T) {
+	num := 5
+	for i := 0; i < num; i++ {
+		a := randResultHistory()
+		b := a.Copy()
+
+		if !reflect.DeepEqual(a, b) {
+			t.Errorf("expected a and b DeepEqual, actual copied map not equal", a, b)
+		}
+
+		// verify a and b don't point to the same map
+		a[enum.CacheName(randStr())] = randResultSlice()
+		if reflect.DeepEqual(a, b) {
+			t.Errorf("expected a != b, actual a and b point to the same map", a)
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/config/config.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/config/config.go b/traffic_monitor_golang/traffic_monitor/config/config.go
new file mode 100644
index 0000000..1941ec6
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/config/config.go
@@ -0,0 +1,186 @@
+package config
+
+/*
+ * 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"
+	"io/ioutil"
+	"time"
+)
+
+// LogLocation is a location to log to. This may be stdout, stderr, null (/dev/null), or a valid file path.
+type LogLocation string
+
+const (
+	// LogLocationStdout indicates the stdout IO stream
+	LogLocationStdout = "stdout"
+	// LogLocationStderr indicates the stderr IO stream
+	LogLocationStderr = "stderr"
+	// LogLocationNull indicates the null IO stream (/dev/null)
+	LogLocationNull = "null"
+	//StaticFileDir is the directory that contains static html and js files.
+	StaticFileDir = "/opt/traffic_monitor/static/"
+)
+
+// Config is the configuration for the application. It includes myriad data, such as polling intervals and log locations.
+type Config struct {
+	CacheHealthPollingInterval   time.Duration `json:"-"`
+	CacheStatPollingInterval     time.Duration `json:"-"`
+	MonitorConfigPollingInterval time.Duration `json:"-"`
+	HTTPTimeout                  time.Duration `json:"-"`
+	PeerPollingInterval          time.Duration `json:"-"`
+	PeerOptimistic               bool          `json:"peer_optimistic"`
+	MaxEvents                    uint64        `json:"max_events"`
+	MaxStatHistory               uint64        `json:"max_stat_history"`
+	MaxHealthHistory             uint64        `json:"max_health_history"`
+	HealthFlushInterval          time.Duration `json:"-"`
+	StatFlushInterval            time.Duration `json:"-"`
+	LogLocationError             string        `json:"log_location_error"`
+	LogLocationWarning           string        `json:"log_location_warning"`
+	LogLocationInfo              string        `json:"log_location_info"`
+	LogLocationDebug             string        `json:"log_location_debug"`
+	LogLocationEvent             string        `json:"log_location_event"`
+	ServeReadTimeout             time.Duration `json:"-"`
+	ServeWriteTimeout            time.Duration `json:"-"`
+	HealthToStatRatio            uint64        `json:"health_to_stat_ratio"`
+	HTTPPollNoSleep              bool          `json:"http_poll_no_sleep"`
+	StaticFileDir                string        `json:"static_file_dir"`
+}
+
+// DefaultConfig is the default configuration for the application, if no configuration file is given, or if a given config setting doesn't exist in the config file.
+var DefaultConfig = Config{
+	CacheHealthPollingInterval:   6 * time.Second,
+	CacheStatPollingInterval:     6 * time.Second,
+	MonitorConfigPollingInterval: 5 * time.Second,
+	HTTPTimeout:                  2 * time.Second,
+	PeerPollingInterval:          5 * time.Second,
+	PeerOptimistic:               true,
+	MaxEvents:                    200,
+	MaxStatHistory:               5,
+	MaxHealthHistory:             5,
+	HealthFlushInterval:          200 * time.Millisecond,
+	StatFlushInterval:            200 * time.Millisecond,
+	LogLocationError:             LogLocationStderr,
+	LogLocationWarning:           LogLocationStdout,
+	LogLocationInfo:              LogLocationNull,
+	LogLocationDebug:             LogLocationNull,
+	LogLocationEvent:             LogLocationStdout,
+	ServeReadTimeout:             10 * time.Second,
+	ServeWriteTimeout:            10 * time.Second,
+	HealthToStatRatio:            4,
+	HTTPPollNoSleep:              false,
+	StaticFileDir:                StaticFileDir,
+}
+
+// MarshalJSON marshals custom millisecond durations. Aliasing inspired by http://choly.ca/post/go-json-marshalling/
+func (c *Config) MarshalJSON() ([]byte, error) {
+	type Alias Config
+	return json.Marshal(&struct {
+		CacheHealthPollingIntervalMs   uint64 `json:"cache_health_polling_interval_ms"`
+		CacheStatPollingIntervalMs     uint64 `json:"cache_stat_polling_interval_ms"`
+		MonitorConfigPollingIntervalMs uint64 `json:"monitor_config_polling_interval_ms"`
+		HTTPTimeoutMS                  uint64 `json:"http_timeout_ms"`
+		PeerPollingIntervalMs          uint64 `json:"peer_polling_interval_ms"`
+		PeerOptimistic                 bool   `json:"peer_optimistic"`
+		HealthFlushIntervalMs          uint64 `json:"health_flush_interval_ms"`
+		StatFlushIntervalMs            uint64 `json:"stat_flush_interval_ms"`
+		ServeReadTimeoutMs             uint64 `json:"serve_read_timeout_ms"`
+		ServeWriteTimeoutMs            uint64 `json:"serve_write_timeout_ms"`
+		*Alias
+	}{
+		CacheHealthPollingIntervalMs:   uint64(c.CacheHealthPollingInterval / time.Millisecond),
+		CacheStatPollingIntervalMs:     uint64(c.CacheStatPollingInterval / time.Millisecond),
+		MonitorConfigPollingIntervalMs: uint64(c.MonitorConfigPollingInterval / time.Millisecond),
+		HTTPTimeoutMS:                  uint64(c.HTTPTimeout / time.Millisecond),
+		PeerPollingIntervalMs:          uint64(c.PeerPollingInterval / time.Millisecond),
+		PeerOptimistic:                 bool(true),
+		HealthFlushIntervalMs:          uint64(c.HealthFlushInterval / time.Millisecond),
+		StatFlushIntervalMs:            uint64(c.StatFlushInterval / time.Millisecond),
+		Alias:                          (*Alias)(c),
+	})
+}
+
+// UnmarshalJSON populates this config object from given JSON bytes.
+func (c *Config) UnmarshalJSON(data []byte) error {
+	type Alias Config
+	aux := &struct {
+		CacheHealthPollingIntervalMs   *uint64 `json:"cache_health_polling_interval_ms"`
+		CacheStatPollingIntervalMs     *uint64 `json:"cache_stat_polling_interval_ms"`
+		MonitorConfigPollingIntervalMs *uint64 `json:"monitor_config_polling_interval_ms"`
+		HTTPTimeoutMS                  *uint64 `json:"http_timeout_ms"`
+		PeerPollingIntervalMs          *uint64 `json:"peer_polling_interval_ms"`
+		PeerOptimistic                 *bool   `json:"peer_optimistic"`
+		HealthFlushIntervalMs          *uint64 `json:"health_flush_interval_ms"`
+		StatFlushIntervalMs            *uint64 `json:"stat_flush_interval_ms"`
+		ServeReadTimeoutMs             *uint64 `json:"serve_read_timeout_ms"`
+		ServeWriteTimeoutMs            *uint64 `json:"serve_write_timeout_ms"`
+		*Alias
+	}{
+		Alias: (*Alias)(c),
+	}
+	if err := json.Unmarshal(data, &aux); err != nil {
+		return err
+	}
+
+	if aux.CacheHealthPollingIntervalMs != nil {
+		c.CacheHealthPollingInterval = time.Duration(*aux.CacheHealthPollingIntervalMs) * time.Millisecond
+	}
+	if aux.CacheStatPollingIntervalMs != nil {
+		c.CacheStatPollingInterval = time.Duration(*aux.CacheStatPollingIntervalMs) * time.Millisecond
+	}
+	if aux.MonitorConfigPollingIntervalMs != nil {
+		c.MonitorConfigPollingInterval = time.Duration(*aux.MonitorConfigPollingIntervalMs) * time.Millisecond
+	}
+	if aux.HTTPTimeoutMS != nil {
+		c.HTTPTimeout = time.Duration(*aux.HTTPTimeoutMS) * time.Millisecond
+	}
+	if aux.PeerPollingIntervalMs != nil {
+		c.PeerPollingInterval = time.Duration(*aux.PeerPollingIntervalMs) * time.Millisecond
+	}
+	if aux.HealthFlushIntervalMs != nil {
+		c.HealthFlushInterval = time.Duration(*aux.HealthFlushIntervalMs) * time.Millisecond
+	}
+	if aux.StatFlushIntervalMs != nil {
+		c.StatFlushInterval = time.Duration(*aux.StatFlushIntervalMs) * time.Millisecond
+	}
+	if aux.ServeReadTimeoutMs != nil {
+		c.ServeReadTimeout = time.Duration(*aux.ServeReadTimeoutMs) * time.Millisecond
+	}
+	if aux.ServeWriteTimeoutMs != nil {
+		c.ServeWriteTimeout = time.Duration(*aux.ServeWriteTimeoutMs) * time.Millisecond
+	}
+	if aux.PeerOptimistic != nil {
+		c.PeerOptimistic = *aux.PeerOptimistic
+	}
+	return nil
+}
+
+// Load loads the given config file. If an empty string is passed, the default config is returned.
+func Load(fileName string) (Config, error) {
+	cfg := DefaultConfig
+	if fileName == "" {
+		return cfg, nil
+	}
+	configBytes, err := ioutil.ReadFile(fileName)
+	if err == nil {
+		err = json.Unmarshal(configBytes, &cfg)
+	}
+	return cfg, err
+}


[12/19] incubator-trafficcontrol git commit: Move TM2 to trafficcontrol/traffic_monitor_golang

Posted by ne...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/threadsafe/polledcaches.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/threadsafe/polledcaches.go b/traffic_monitor/experimental/traffic_monitor/threadsafe/polledcaches.go
deleted file mode 100644
index ff987b7..0000000
--- a/traffic_monitor/experimental/traffic_monitor/threadsafe/polledcaches.go
+++ /dev/null
@@ -1,147 +0,0 @@
-package threadsafe
-
-/*
- * 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 (
-	"sync"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/cache"
-	dsdata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/deliveryservicedata"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-)
-
-// UnpolledCaches is a structure containing a map of caches which have yet to be polled, which is threadsafe for multiple readers and one writer.
-// This could be made lock-free, if the performance was necessary
-type UnpolledCaches struct {
-	unpolledCaches *map[enum.CacheName]struct{}
-	allCaches      *map[enum.CacheName]struct{}
-	initialized    *bool
-	m              *sync.RWMutex
-}
-
-// NewUnpolledCaches returns a new UnpolledCaches object.
-func NewUnpolledCaches() UnpolledCaches {
-	b := false
-	return UnpolledCaches{
-		m:              &sync.RWMutex{},
-		unpolledCaches: &map[enum.CacheName]struct{}{},
-		allCaches:      &map[enum.CacheName]struct{}{},
-		initialized:    &b,
-	}
-}
-
-// UnpolledCaches returns a map of caches not yet polled. Callers MUST NOT modify. If mutation is necessary, copy the map
-func (t *UnpolledCaches) UnpolledCaches() map[enum.CacheName]struct{} {
-	t.m.RLock()
-	defer t.m.RUnlock()
-	return *t.unpolledCaches
-}
-
-// setUnpolledCaches sets the internal unpolled caches map. This is only safe for one thread of execution. This MUST NOT be called from multiple threads.
-func (t *UnpolledCaches) setUnpolledCaches(v map[enum.CacheName]struct{}) {
-	t.m.Lock()
-	*t.initialized = true
-	*t.unpolledCaches = v
-	t.m.Unlock()
-}
-
-// SetNewCaches takes a list of new caches, which may overlap with the existing caches, diffs them, removes any `unpolledCaches` which aren't in the new list, and sets the list of `polledCaches` (which is only used by this func) to the `newCaches`. This is threadsafe with one writer, along with `setUnpolledCaches`.
-func (t *UnpolledCaches) SetNewCaches(newCaches map[enum.CacheName]struct{}) {
-	unpolledCaches := copyCaches(t.UnpolledCaches())
-	allCaches := copyCaches(*t.allCaches) // not necessary to lock `allCaches`, as the single-writer is the only thing that accesses it.
-	for cache := range unpolledCaches {
-		if _, ok := newCaches[cache]; !ok {
-			delete(unpolledCaches, cache)
-		}
-	}
-	for cache := range allCaches {
-		if _, ok := newCaches[cache]; !ok {
-			delete(allCaches, cache)
-		}
-	}
-	for cache := range newCaches {
-		if _, ok := allCaches[cache]; !ok {
-			unpolledCaches[cache] = struct{}{}
-			allCaches[cache] = struct{}{}
-		}
-	}
-	*t.allCaches = allCaches
-	t.setUnpolledCaches(unpolledCaches)
-}
-
-// Any returns whether there are any caches marked as not polled. Also returns true if SetNewCaches() has never been called (assuming there exist caches, if this hasn't been initialized, we couldn't have polled any of them).
-func (t *UnpolledCaches) Any() bool {
-	t.m.Lock()
-	defer t.m.Unlock()
-	return !(*t.initialized) || len(*t.unpolledCaches) > 0
-}
-
-// copyCaches performs a deep copy of the given map.
-func copyCaches(a map[enum.CacheName]struct{}) map[enum.CacheName]struct{} {
-	b := map[enum.CacheName]struct{}{}
-	for k := range a {
-		b[k] = struct{}{}
-	}
-	return b
-}
-
-// SetPolled sets cache which have been polled. This is used to determine when the app has fully started up, and we can start serving. Serving Traffic Router with caches as 'down' which simply haven't been polled yet would be bad. Therefore, a cache is set as 'polled' if it has received different bandwidths from two different ATS ticks, OR if the cache is marked as down (and thus we won't get a bandwidth).
-// This is threadsafe for one writer, along with `Set`.
-// This is fast if there are no unpolled caches. Moreover, its speed is a function of the number of unpolled caches, not the number of caches total.
-func (t *UnpolledCaches) SetPolled(results []cache.Result, lastStats dsdata.LastStats) {
-	unpolledCaches := copyCaches(t.UnpolledCaches())
-	numUnpolledCaches := len(unpolledCaches)
-	if numUnpolledCaches == 0 {
-		return
-	}
-	for cache := range unpolledCaches {
-	innerLoop:
-		for _, result := range results {
-			if result.ID != cache {
-				continue
-			}
-
-			if !result.Available || result.Error != nil {
-				log.Debugf("polled %v\n", cache)
-				delete(unpolledCaches, cache)
-				break innerLoop
-			}
-		}
-		lastStat, ok := lastStats.Caches[cache]
-		if !ok {
-			continue
-		}
-		if lastStat.Bytes.PerSec != 0 {
-			log.Debugf("polled %v\n", cache)
-			delete(unpolledCaches, cache)
-		}
-	}
-
-	if len(unpolledCaches) == numUnpolledCaches {
-		return
-	}
-	t.setUnpolledCaches(unpolledCaches)
-	if len(unpolledCaches) != 0 {
-		log.Infof("remaining unpolled %v\n", unpolledCaches)
-	} else {
-		log.Infof("all caches polled, ready to serve!\n")
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/threadsafe/resulthistory.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/threadsafe/resulthistory.go b/traffic_monitor/experimental/traffic_monitor/threadsafe/resulthistory.go
deleted file mode 100644
index f3f82e0..0000000
--- a/traffic_monitor/experimental/traffic_monitor/threadsafe/resulthistory.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package threadsafe
-
-/*
- * 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 (
-	"sync"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/cache"
-)
-
-// ResultHistory provides safe access for multiple goroutines readers and a single writer to a stored ResultHistory object.
-// This could be made lock-free, if the performance was necessary
-// TODO add separate locks for Caches and Deliveryservice maps?
-type ResultHistory struct {
-	resultHistory *cache.ResultHistory
-	m             *sync.RWMutex
-}
-
-// NewResultHistory returns a new ResultHistory safe for multiple readers and a single writer.
-func NewResultHistory() ResultHistory {
-	h := cache.ResultHistory{}
-	return ResultHistory{m: &sync.RWMutex{}, resultHistory: &h}
-}
-
-// Get returns the ResultHistory. Callers MUST NOT modify. If mutation is necessary, call ResultHistory.Copy()
-func (h *ResultHistory) Get() cache.ResultHistory {
-	h.m.RLock()
-	defer h.m.RUnlock()
-	return *h.resultHistory
-}
-
-// Set sets the internal ResultHistory. This is only safe for one thread of execution. This MUST NOT be called from multiple threads.
-func (h *ResultHistory) Set(v cache.ResultHistory) {
-	h.m.Lock()
-	*h.resultHistory = v
-	h.m.Unlock()
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/threadsafe/resultstathistory.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/threadsafe/resultstathistory.go b/traffic_monitor/experimental/traffic_monitor/threadsafe/resultstathistory.go
deleted file mode 100644
index cf8f996..0000000
--- a/traffic_monitor/experimental/traffic_monitor/threadsafe/resultstathistory.go
+++ /dev/null
@@ -1,81 +0,0 @@
-// TODO rename
-package threadsafe
-
-/*
- * 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 (
-	"sync"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/cache"
-)
-
-// ResultStatHistory provides safe access for multiple goroutines readers and a single writer to a stored HistoryHistory object.
-// This could be made lock-free, if the performance was necessary
-type ResultStatHistory struct {
-	history *cache.ResultStatHistory
-	m       *sync.RWMutex
-}
-
-// NewResultStatHistory returns a new ResultStatHistory safe for multiple readers and a single writer.
-func NewResultStatHistory() ResultStatHistory {
-	h := cache.ResultStatHistory{}
-	return ResultStatHistory{m: &sync.RWMutex{}, history: &h}
-}
-
-// Get returns the ResultStatHistory. Callers MUST NOT modify. If mutation is necessary, call ResultStatHistory.Copy()
-func (h *ResultStatHistory) Get() cache.ResultStatHistory {
-	h.m.RLock()
-	defer h.m.RUnlock()
-	return *h.history
-}
-
-// Set sets the internal ResultStatHistory. This is only safe for one thread of execution. This MUST NOT be called from multiple threads.
-func (h *ResultStatHistory) Set(v cache.ResultStatHistory) {
-	h.m.Lock()
-	*h.history = v
-	h.m.Unlock()
-}
-
-// ResultStatHistory provides safe access for multiple goroutines readers and a single writer to a stored HistoryHistory object.
-// This could be made lock-free, if the performance was necessary
-type ResultInfoHistory struct {
-	history *cache.ResultInfoHistory
-	m       *sync.RWMutex
-}
-
-// NewResultInfoHistory returns a new ResultInfoHistory safe for multiple readers and a single writer.
-func NewResultInfoHistory() ResultInfoHistory {
-	h := cache.ResultInfoHistory{}
-	return ResultInfoHistory{m: &sync.RWMutex{}, history: &h}
-}
-
-// Get returns the ResultInfoHistory. Callers MUST NOT modify. If mutation is necessary, call ResultInfoHistory.Copy()
-func (h *ResultInfoHistory) Get() cache.ResultInfoHistory {
-	h.m.RLock()
-	defer h.m.RUnlock()
-	return *h.history
-}
-
-// Set sets the internal ResultInfoHistory. This is only safe for one thread of execution. This MUST NOT be called from multiple threads.
-func (h *ResultInfoHistory) Set(v cache.ResultInfoHistory) {
-	h.m.Lock()
-	*h.history = v
-	h.m.Unlock()
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/threadsafe/uint.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/threadsafe/uint.go b/traffic_monitor/experimental/traffic_monitor/threadsafe/uint.go
deleted file mode 100644
index fcaa3a4..0000000
--- a/traffic_monitor/experimental/traffic_monitor/threadsafe/uint.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package threadsafe
-
-/*
- * 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 (
-	"sync/atomic"
-)
-
-// Uint provides safe access for multiple goroutines readers and a single writer to a stored uint.
-type Uint struct {
-	val *uint64
-}
-
-// NewUint returns a new single-writer-multiple-reader threadsafe uint
-func NewUint() Uint {
-	v := uint64(0)
-	return Uint{val: &v}
-}
-
-// Get gets the internal uint. This is safe for multiple readers
-func (u *Uint) Get() uint64 {
-	return atomic.LoadUint64(u.val)
-}
-
-// Set sets the internal uint. This MUST NOT be called by multiple goroutines.
-func (u *Uint) Set(v uint64) {
-	atomic.StoreUint64(u.val, v)
-}
-
-// Inc increments the internal uint64.
-// TODO make sure everything using this uses the value it returns, not a separate Get
-func (u *Uint) Inc() uint64 {
-	return atomic.AddUint64(u.val, 1)
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/traffic_monitor.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/traffic_monitor.go b/traffic_monitor/experimental/traffic_monitor/traffic_monitor.go
deleted file mode 100644
index f97cf3d..0000000
--- a/traffic_monitor/experimental/traffic_monitor/traffic_monitor.go
+++ /dev/null
@@ -1,157 +0,0 @@
-package main
-
-/*
- * 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 (
-	"bytes"
-	"flag"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"math"
-	"os"
-	"os/exec"
-	"runtime"
-	"time"
-
-	_ "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/instrumentation"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/config"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/manager"
-	_ "github.com/davecheney/gmx"
-)
-
-// GitRevision is the git revision of the app. The app SHOULD always be built with this set via the `-X` flag.
-var GitRevision = "No Git Revision Specified. Please build with '-X main.GitRevision=${git rev-parse HEAD}'"
-
-// 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
-	}
-
-	return d, nil
-}
-
-func getLogWriter(location string) (io.Writer, error) {
-	switch location {
-	case config.LogLocationStdout:
-		return os.Stdout, nil
-	case config.LogLocationStderr:
-		return os.Stderr, nil
-	case config.LogLocationNull:
-		return ioutil.Discard, nil
-	default:
-		return os.OpenFile(location, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
-	}
-}
-func getLogWriters(eventLoc, errLoc, warnLoc, infoLoc, debugLoc string) (io.Writer, io.Writer, io.Writer, io.Writer, io.Writer, error) {
-	eventW, err := getLogWriter(eventLoc)
-	if err != nil {
-		return nil, nil, nil, nil, nil, fmt.Errorf("getting log event writer %v: %v", eventLoc, err)
-	}
-	errW, err := getLogWriter(errLoc)
-	if err != nil {
-		return nil, nil, nil, nil, nil, fmt.Errorf("getting log error writer %v: %v", errLoc, err)
-	}
-	warnW, err := getLogWriter(warnLoc)
-	if err != nil {
-		return nil, nil, nil, nil, nil, fmt.Errorf("getting log warning writer %v: %v", warnLoc, err)
-	}
-	infoW, err := getLogWriter(infoLoc)
-	if err != nil {
-		return nil, nil, nil, nil, nil, fmt.Errorf("getting log info writer %v: %v", infoLoc, err)
-	}
-	debugW, err := getLogWriter(debugLoc)
-	if err != nil {
-		return nil, nil, nil, nil, nil, fmt.Errorf("getting log debug writer %v: %v", debugLoc, err)
-	}
-	return eventW, errW, warnW, infoW, debugW, nil
-}
-
-func main() {
-	runtime.GOMAXPROCS(runtime.NumCPU())
-
-	staticData, err := getStaticAppData()
-	if err != nil {
-		fmt.Printf("Error starting service: failed to get static app data: %v\n", err)
-		os.Exit(1)
-	}
-
-	opsConfigFile := flag.String("opsCfg", "", "The traffic ops config file")
-	configFileName := flag.String("config", "", "The Traffic Monitor config file path")
-	flag.Parse()
-
-	if *opsConfigFile == "" {
-		fmt.Println("Error starting service: The --opsCfg argument is required")
-		os.Exit(1)
-	}
-
-	// TODO add hot reloading (like opsConfigFile)?
-	cfg, err := config.Load(*configFileName)
-	if err != nil {
-		fmt.Printf("Error starting service: failed to load config: %v\n", err)
-		os.Exit(1)
-	}
-
-	eventW, errW, warnW, infoW, debugW, err := getLogWriters(cfg.LogLocationEvent, cfg.LogLocationError, cfg.LogLocationWarning, cfg.LogLocationInfo, cfg.LogLocationDebug)
-	if err != nil {
-		fmt.Printf("Error starting service: failed to create log writers: %v\n", err)
-		os.Exit(1)
-	}
-	log.Init(eventW, errW, warnW, infoW, debugW)
-
-	log.Infof("Starting with config %+v\n", cfg)
-
-	manager.Start(*opsConfigFile, cfg, staticData)
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/trafficopsdata/trafficopsdata.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/trafficopsdata/trafficopsdata.go b/traffic_monitor/experimental/traffic_monitor/trafficopsdata/trafficopsdata.go
deleted file mode 100644
index cefb441..0000000
--- a/traffic_monitor/experimental/traffic_monitor/trafficopsdata/trafficopsdata.go
+++ /dev/null
@@ -1,290 +0,0 @@
-package trafficopsdata
-
-/*
- * 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"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-	towrap "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/trafficopswrapper"
-	"regexp"
-	"strings"
-	"sync"
-)
-
-// Regexes maps Delivery Service Regular Expressions to delivery services.
-// For performance, we categorize Regular Expressions into 3 categories:
-// 1. Direct string matches, with no regular expression matching characters
-// 2. .*\.foo\..* expressions, where foo is a direct string match with no regular expression matching characters
-// 3. Everything else
-// This allows us to do a cheap match on 1 and 2, and only regex match the uncommon case.
-// TODO performance tests, whether Go compiled *Regexp is relevantly slower than `strings.Contains` for direct and .foo. matches
-type Regexes struct {
-	DirectMatches                      map[string]enum.DeliveryServiceName
-	DotStartSlashDotFooSlashDotDotStar map[string]enum.DeliveryServiceName
-	RegexMatch                         map[*regexp.Regexp]enum.DeliveryServiceName
-}
-
-// DeliveryService returns the delivery service which matches the given fqdn, or false.
-func (d Regexes) DeliveryService(fqdn string) (enum.DeliveryServiceName, bool) {
-	if ds, ok := d.DirectMatches[fqdn]; ok {
-		return ds, true
-	}
-	for matchStr, ds := range d.DotStartSlashDotFooSlashDotDotStar {
-		if strings.Contains(fqdn, "."+matchStr+".") {
-			return ds, true
-		}
-	}
-	for regex, ds := range d.RegexMatch {
-		if regex.MatchString(fqdn) {
-			return ds, true
-		}
-	}
-	return "", false
-}
-
-// NewRegexes constructs a new Regexes object, initializing internal pointer members.
-func NewRegexes() Regexes {
-	return Regexes{DirectMatches: map[string]enum.DeliveryServiceName{}, DotStartSlashDotFooSlashDotDotStar: map[string]enum.DeliveryServiceName{}, RegexMatch: map[*regexp.Regexp]enum.DeliveryServiceName{}}
-}
-
-// TOData holds CDN data fetched from Traffic Ops.
-type TOData struct {
-	DeliveryServiceServers map[enum.DeliveryServiceName][]enum.CacheName
-	ServerDeliveryServices map[enum.CacheName][]enum.DeliveryServiceName
-	ServerTypes            map[enum.CacheName]enum.CacheType
-	DeliveryServiceTypes   map[enum.DeliveryServiceName]enum.DSType
-	DeliveryServiceRegexes Regexes
-	ServerCachegroups      map[enum.CacheName]enum.CacheGroupName
-}
-
-// New returns a new empty TOData object, initializing pointer members.
-func New() *TOData {
-	return &TOData{
-		DeliveryServiceServers: map[enum.DeliveryServiceName][]enum.CacheName{},
-		ServerDeliveryServices: map[enum.CacheName][]enum.DeliveryServiceName{},
-		ServerTypes:            map[enum.CacheName]enum.CacheType{},
-		DeliveryServiceTypes:   map[enum.DeliveryServiceName]enum.DSType{},
-		DeliveryServiceRegexes: NewRegexes(),
-		ServerCachegroups:      map[enum.CacheName]enum.CacheGroupName{},
-	}
-}
-
-// TODataThreadsafe provides safe access for multiple goroutine writers and one goroutine reader, to the encapsulated TOData object.
-// This could be made lock-free, if the performance was necessary
-type TODataThreadsafe struct {
-	toData *TOData
-	m      *sync.RWMutex
-}
-
-// NewThreadsafe returns a new TOData object, wrapped to be safe for multiple goroutine readers and a single writer.
-func NewThreadsafe() TODataThreadsafe {
-	return TODataThreadsafe{m: &sync.RWMutex{}, toData: New()}
-}
-
-// Get returns the current TOData. Callers MUST NOT modify returned data. Mutation IS NOT threadsafe
-// If callers need to modify, a new GetMutable() should be added which copies.
-func (d TODataThreadsafe) Get() TOData {
-	d.m.RLock()
-	defer d.m.RUnlock()
-	return *d.toData
-}
-
-func (d TODataThreadsafe) set(newTOData TOData) {
-	d.m.Lock()
-	*d.toData = newTOData
-	d.m.Unlock()
-}
-
-// CRConfig is the CrConfig data needed by TOData. Note this is not all data in the CRConfig.
-// TODO change strings to type?
-type CRConfig struct {
-	ContentServers map[enum.CacheName]struct {
-		DeliveryServices map[enum.DeliveryServiceName][]string `json:"deliveryServices"`
-		CacheGroup       string                                `json:"cacheGroup"`
-		Type             string                                `json:"type"`
-	} `json:"contentServers"`
-	DeliveryServices map[enum.DeliveryServiceName]struct {
-		Matchsets []struct {
-			Protocol  string `json:"protocol"`
-			MatchList []struct {
-				Regex string `json:"regex"`
-			} `json:"matchlist"`
-		} `json:"matchsets"`
-	} `json:"deliveryServices"`
-}
-
-// Fetch gets the CRConfig from Traffic Ops, creates the TOData maps, and atomically sets the TOData.
-// TODO since the session is threadsafe, each TOData get func below could be put in a goroutine, if performance mattered
-func (d TODataThreadsafe) Fetch(to towrap.ITrafficOpsSession, cdn string) error {
-	newTOData := TOData{}
-
-	crConfigBytes, err := to.CRConfigRaw(cdn)
-	if err != nil {
-		return fmt.Errorf("Error getting CRconfig from Traffic Ops: %v", err)
-	}
-	var crConfig CRConfig
-	err = json.Unmarshal(crConfigBytes, &crConfig)
-	if err != nil {
-		return fmt.Errorf("Error unmarshalling CRconfig: %v", err)
-	}
-
-	newTOData.DeliveryServiceServers, newTOData.ServerDeliveryServices, err = getDeliveryServiceServers(crConfig)
-	if err != nil {
-		return err
-	}
-
-	newTOData.DeliveryServiceTypes, err = getDeliveryServiceTypes(crConfig)
-	if err != nil {
-		return fmt.Errorf("Error getting delivery service types from Traffic Ops: %v\n", err)
-	}
-
-	newTOData.DeliveryServiceRegexes, err = getDeliveryServiceRegexes(crConfig)
-	if err != nil {
-		return fmt.Errorf("Error getting delivery service regexes from Traffic Ops: %v\n", err)
-	}
-
-	newTOData.ServerCachegroups, err = getServerCachegroups(crConfig)
-	if err != nil {
-		return fmt.Errorf("Error getting server cachegroups from Traffic Ops: %v\n", err)
-	}
-
-	newTOData.ServerTypes, err = getServerTypes(crConfig)
-	if err != nil {
-		return fmt.Errorf("Error getting server types from Traffic Ops: %v\n", err)
-	}
-
-	d.set(newTOData)
-	return nil
-}
-
-// getDeliveryServiceServers gets the servers on each delivery services, for the given CDN, from Traffic Ops.
-func getDeliveryServiceServers(crc CRConfig) (map[enum.DeliveryServiceName][]enum.CacheName, map[enum.CacheName][]enum.DeliveryServiceName, error) {
-	dsServers := map[enum.DeliveryServiceName][]enum.CacheName{}
-	serverDses := map[enum.CacheName][]enum.DeliveryServiceName{}
-
-	for serverName, serverData := range crc.ContentServers {
-		for deliveryServiceName := range serverData.DeliveryServices {
-			dsServers[deliveryServiceName] = append(dsServers[deliveryServiceName], serverName)
-			serverDses[serverName] = append(serverDses[serverName], deliveryServiceName)
-		}
-	}
-	return dsServers, serverDses, nil
-}
-
-// getDeliveryServiceRegexes gets the regexes of each delivery service, for the given CDN, from Traffic Ops.
-// Returns a map[deliveryService][]regex.
-func getDeliveryServiceRegexes(crc CRConfig) (Regexes, error) {
-	dsRegexes := map[enum.DeliveryServiceName][]string{}
-
-	for dsName, dsData := range crc.DeliveryServices {
-		if len(dsData.Matchsets) < 1 {
-			return Regexes{}, fmt.Errorf("CRConfig missing regex for '%s'", dsName)
-		}
-		for _, matchset := range dsData.Matchsets {
-			if len(matchset.MatchList) < 1 {
-				return Regexes{}, fmt.Errorf("CRConfig missing Regex for '%s'", dsName)
-			}
-			dsRegexes[dsName] = append(dsRegexes[dsName], matchset.MatchList[0].Regex)
-		}
-	}
-
-	return createRegexes(dsRegexes)
-}
-
-// TODO precompute, move to TOData; call when we get new delivery services, instead of every time we create new stats
-func createRegexes(dsToRegex map[enum.DeliveryServiceName][]string) (Regexes, error) {
-	dsRegexes := Regexes{
-		DirectMatches:                      map[string]enum.DeliveryServiceName{},
-		DotStartSlashDotFooSlashDotDotStar: map[string]enum.DeliveryServiceName{},
-		RegexMatch:                         map[*regexp.Regexp]enum.DeliveryServiceName{},
-	}
-
-	for ds, regexStrs := range dsToRegex {
-		for _, regexStr := range regexStrs {
-			prefix := `.*\.`
-			suffix := `\..*`
-			if strings.HasPrefix(regexStr, prefix) && strings.HasSuffix(regexStr, suffix) {
-				matchStr := regexStr[len(prefix) : len(regexStr)-len(suffix)]
-				if otherDs, ok := dsRegexes.DotStartSlashDotFooSlashDotDotStar[matchStr]; ok {
-					return dsRegexes, fmt.Errorf("duplicate regex %s (%s) in %s and %s", regexStr, matchStr, ds, otherDs)
-				}
-				dsRegexes.DotStartSlashDotFooSlashDotDotStar[matchStr] = ds
-				continue
-			}
-			if !strings.ContainsAny(regexStr, `[]^\:{}()|?+*,=%@<>!'`) {
-				if otherDs, ok := dsRegexes.DirectMatches[regexStr]; ok {
-					return dsRegexes, fmt.Errorf("duplicate Regex %s in %s and %s", regexStr, ds, otherDs)
-				}
-				dsRegexes.DirectMatches[regexStr] = ds
-				continue
-			}
-			// TODO warn? regex matches are unusual
-			r, err := regexp.Compile(regexStr)
-			if err != nil {
-				return dsRegexes, fmt.Errorf("regex %s failed to compile: %v", regexStr, err)
-			}
-			dsRegexes.RegexMatch[r] = ds
-		}
-	}
-	return dsRegexes, nil
-}
-
-// getServerCachegroups gets the cachegroup of each ATS Edge+Mid Cache server, for the given CDN, from Traffic Ops.
-// Returns a map[server]cachegroup.
-func getServerCachegroups(crc CRConfig) (map[enum.CacheName]enum.CacheGroupName, error) {
-	serverCachegroups := map[enum.CacheName]enum.CacheGroupName{}
-
-	for server, serverData := range crc.ContentServers {
-		serverCachegroups[server] = enum.CacheGroupName(serverData.CacheGroup)
-	}
-	return serverCachegroups, nil
-}
-
-// getServerTypes gets the cache type of each ATS Edge+Mid Cache server, for the given CDN, from Traffic Ops.
-func getServerTypes(crc CRConfig) (map[enum.CacheName]enum.CacheType, error) {
-	serverTypes := map[enum.CacheName]enum.CacheType{}
-
-	for server, serverData := range crc.ContentServers {
-		t := enum.CacheTypeFromString(serverData.Type)
-		if t == enum.CacheTypeInvalid {
-			return nil, fmt.Errorf("getServerTypes CRConfig unknown type for '%s': '%s'", server, serverData.Type)
-		}
-		serverTypes[server] = t
-	}
-	return serverTypes, nil
-}
-
-func getDeliveryServiceTypes(crc CRConfig) (map[enum.DeliveryServiceName]enum.DSType, error) {
-	dsTypes := map[enum.DeliveryServiceName]enum.DSType{}
-
-	for dsName, dsData := range crc.DeliveryServices {
-		if len(dsData.Matchsets) < 1 {
-			return nil, fmt.Errorf("CRConfig missing protocol for '%s'", dsName)
-		}
-		dsTypeStr := dsData.Matchsets[0].Protocol
-		dsType := enum.DSTypeFromString(dsTypeStr)
-		if dsType == enum.DSTypeInvalid {
-			return nil, fmt.Errorf("CRConfig unknowng protocol for '%s': '%s'", dsName, dsTypeStr)
-		}
-		dsTypes[dsName] = dsType
-	}
-	return dsTypes, nil
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/trafficopswrapper/trafficopswrapper.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/trafficopswrapper/trafficopswrapper.go b/traffic_monitor/experimental/traffic_monitor/trafficopswrapper/trafficopswrapper.go
deleted file mode 100644
index 3c48543..0000000
--- a/traffic_monitor/experimental/traffic_monitor/trafficopswrapper/trafficopswrapper.go
+++ /dev/null
@@ -1,128 +0,0 @@
-package trafficopswrapper
-
-/*
- * 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"
-	"sync"
-
-	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
-)
-
-// ITrafficOpsSession provides an interface to the Traffic Ops client, so it may be wrapped or mocked.
-type ITrafficOpsSession interface {
-	CRConfigRaw(cdn string) ([]byte, error)
-	TrafficMonitorConfigMap(cdn string) (*to.TrafficMonitorConfigMap, error)
-	Set(session *to.Session)
-	URL() (string, error)
-	User() (string, error)
-	Servers() ([]to.Server, error)
-	Parameters(profileName string) ([]to.Parameter, error)
-	DeliveryServices() ([]to.DeliveryService, error)
-}
-
-var ErrNilSession = fmt.Errorf("nil session")
-
-func (s TrafficOpsSessionThreadsafe) URL() (string, error) {
-	s.m.Lock()
-	defer s.m.Unlock()
-	if s.session == nil || *s.session == nil {
-		return "", ErrNilSession
-	}
-	url := (*s.session).URL
-	return url, nil
-}
-
-func (s TrafficOpsSessionThreadsafe) User() (string, error) {
-	s.m.Lock()
-	defer s.m.Unlock()
-	if s.session == nil || *s.session == nil {
-		return "", ErrNilSession
-	}
-	user := (*s.session).UserName
-	return user, nil
-}
-
-// TrafficOpsSessionThreadsafe provides access to the Traffic Ops client safe for multiple goroutines. This fulfills the ITrafficOpsSession interface.
-type TrafficOpsSessionThreadsafe struct {
-	session **to.Session // pointer-to-pointer, because we're given a pointer from the Traffic Ops package, and we don't want to copy it.
-	m       *sync.Mutex
-}
-
-// NewTrafficOpsSessionThreadsafe returns a new threadsafe TrafficOpsSessionThreadsafe wrapping the given `Session`.
-func NewTrafficOpsSessionThreadsafe(s *to.Session) TrafficOpsSessionThreadsafe {
-	return TrafficOpsSessionThreadsafe{&s, &sync.Mutex{}}
-}
-
-// CRConfigRaw returns the CRConfig from the Traffic Ops. This is safe for multiple goroutines.
-func (s TrafficOpsSessionThreadsafe) CRConfigRaw(cdn string) ([]byte, error) {
-	s.m.Lock()
-	defer s.m.Unlock()
-	if s.session == nil || *s.session == nil {
-		return nil, ErrNilSession
-	}
-	b, _, e := (*s.session).GetCRConfig(cdn)
-	return b, e
-}
-
-// TrafficMonitorConfigMap returns the Traffic Monitor config map from the Traffic Ops. This is safe for multiple goroutines.
-func (s TrafficOpsSessionThreadsafe) TrafficMonitorConfigMap(cdn string) (*to.TrafficMonitorConfigMap, error) {
-	s.m.Lock()
-	defer s.m.Unlock()
-	if s.session == nil || *s.session == nil {
-		return nil, ErrNilSession
-	}
-	d, e := (*s.session).TrafficMonitorConfigMap(cdn)
-	return d, e
-}
-
-// Set sets the internal Traffic Ops session. This is safe for multiple goroutines, being aware they will race.
-func (s TrafficOpsSessionThreadsafe) Set(session *to.Session) {
-	s.m.Lock()
-	defer s.m.Unlock()
-	*s.session = session
-}
-
-func (s TrafficOpsSessionThreadsafe) Servers() ([]to.Server, error) {
-	s.m.Lock()
-	defer s.m.Unlock()
-	if s.session == nil || *s.session == nil {
-		return nil, ErrNilSession
-	}
-	return (*s.session).Servers()
-}
-
-func (s TrafficOpsSessionThreadsafe) Parameters(profileName string) ([]to.Parameter, error) {
-	s.m.Lock()
-	defer s.m.Unlock()
-	if s.session == nil || *s.session == nil {
-		return nil, ErrNilSession
-	}
-	return (*s.session).Parameters(profileName)
-}
-
-func (s TrafficOpsSessionThreadsafe) DeliveryServices() ([]to.DeliveryService, error) {
-	s.m.Lock()
-	defer s.m.Unlock()
-	if s.session == nil || *s.session == nil {
-		return nil, ErrNilSession
-	}
-	return (*s.session).DeliveryServices()
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/version.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/version.go b/traffic_monitor/experimental/traffic_monitor/version.go
deleted file mode 100644
index c1ecc0c..0000000
--- a/traffic_monitor/experimental/traffic_monitor/version.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package main
-
-/*
- * 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.
- */
-
-// Version is the current version of the app, in string form.
-var Version = "2.0.1"

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/.gitignore
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/.gitignore b/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/.gitignore
deleted file mode 100644
index 19c3660..0000000
--- a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-gmxc/gmxc

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/LICENCE
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/LICENCE b/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/LICENCE
deleted file mode 100644
index d7f4f29..0000000
--- a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/LICENCE
+++ /dev/null
@@ -1,8 +0,0 @@
-Copyright (c) 2012, David Cheney
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
-Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/README.md
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/README.md b/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/README.md
deleted file mode 100644
index b66d10c..0000000
--- a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/README.md
+++ /dev/null
@@ -1,52 +0,0 @@
-# Go management extensions
-
-## Installation
-  
-	go get github.com/davecheney/gmx
-
-## Getting started
-
-Instrumenting your application with gmx is as simple as importing the `gmx` package in your `main` package via the side effect operator.
-
-	package main
-
-	import _ "github.com/davecheney/gmx"
-
-By default gmx opens a unix socket in `/tmp`, the name of the socket is
-
-	/tmp/.gmx.$PID.0
-
-## Protocol version 0
-
-The current protocol version is 0, which is a simple JSON based protocol. You can communicate with the gmx socket using a tool like socat.
-
-	% socat UNIX-CONNECT:/tmp/.gmx.$(pgrep godoc).0 stdin
-	["runtime.version", "runtime.numcpu"]
-	{"runtime.numcpu":4,"runtime.version":"weekly.2012-01-27 11688+"}
-     
-The request is a json array of strings representing keys that you wish to query. The result is a json map, the keys of that map are keys that matched the keys in your request. The value of the entry will be the result of the published function, encoded in json. If there is no matching key registered, no entry will appear in the result map.
-
-For convenience a client is included in the gmxc sub directory. Please consult the `README` in that directory for more details.
-
-## Registering gmx keys
-
-New keys can be registered using the `Publish` function
-
-	gmx.Publish(key string, f func() interface{})
-
-`f` can be any function that returns a json encodable result. `f` is executed whenever its key is invoked, responsibility for ensuring the function is thread safe rests with the author of `f`.
-
-## Runtime instrumentation
-
-By default gmx instruments selected values from the  `runtime` and `os` packages, refer to the `runtime.go` and `os.go` source for more details.
-
-## Changelog
-
-6/Feb/2012 
-
-+	gmx now honors the value of os.TempDir() when opening the unix socket
-+	gmxc now accepts regexps for key names
-
-5/Feb/2012 
-
-+	Initial release

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/example/helloworld/README.md
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/example/helloworld/README.md b/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/example/helloworld/README.md
deleted file mode 100644
index 6cc9401..0000000
--- a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/example/helloworld/README.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# helloworld example
-
-Install the gmxc client
-
-	go get github.com/davecheney/gmx/gmxc
-
-Install this example 
-
-	go get github.com/davecheney/gmx/example/helloworld
-
-Run the example in the background
-
-	$GOBIN/helloworld &
-
-Query it via gmxc
-
-	$GOBIN/gmxc -p $(pgrep helloworld) hello
-	hello: world
-

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/example/helloworld/main.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/example/helloworld/main.go b/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/example/helloworld/main.go
deleted file mode 100644
index f6d2765..0000000
--- a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/example/helloworld/main.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package main
-
-import "github.com/davecheney/gmx"
-
-func init() {
-	gmx.Publish("hello", func() interface{} {
-		return "world"
-	})
-}
-
-func main() {
-	// sleep forever
-	select {}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/gmx.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/gmx.go b/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/gmx.go
deleted file mode 100644
index 5b446fc..0000000
--- a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/gmx.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package gmx
-
-import (
-	"encoding/json"
-	"fmt"
-	"io"
-	"log"
-	"net"
-	"os"
-	"path/filepath"
-	"sync"
-)
-
-const GMX_VERSION = 0
-
-var (
-	r = &registry{
-		entries: make(map[string]func() interface{}),
-	}
-)
-
-func init() {
-	s, err := localSocket()
-	if err != nil {
-		log.Printf("gmx: unable to open local socket: %v", err)
-		return
-	}
-
-	// register the registries keys for discovery
-	Publish("keys", func() interface{} {
-		return r.keys()
-	})
-	go serve(s, r)
-}
-
-func localSocket() (net.Listener, error) {
-	return net.ListenUnix("unix", localSocketAddr())
-}
-
-func localSocketAddr() *net.UnixAddr {
-	return &net.UnixAddr{
-		filepath.Join(os.TempDir(), fmt.Sprintf(".gmx.%d.%d", os.Getpid(), GMX_VERSION)),
-		"unix",
-	}
-}
-
-// Publish registers the function f with the supplied key.
-func Publish(key string, f func() interface{}) {
-	r.register(key, f)
-}
-
-func serve(l net.Listener, r *registry) {
-	// if listener is a unix socket, try to delete it on shutdown
-	if l, ok := l.(*net.UnixListener); ok {
-		if a, ok := l.Addr().(*net.UnixAddr); ok {
-			defer os.Remove(a.Name)
-		}
-	}
-	defer l.Close()
-	for {
-		c, err := l.Accept()
-		if err != nil {
-			return
-		}
-		go handle(c, r)
-	}
-}
-
-func handle(nc net.Conn, reg *registry) {
-	// conn makes it easier to send and receive json
-	type conn struct {
-		net.Conn
-		*json.Encoder
-		*json.Decoder
-	}
-	c := conn{
-		nc,
-		json.NewEncoder(nc),
-		json.NewDecoder(nc),
-	}
-	defer c.Close()
-	for {
-		var keys []string
-		if err := c.Decode(&keys); err != nil {
-			if err != io.EOF {
-				log.Printf("gmx: client %v sent invalid json request: %v", c.RemoteAddr(), err)
-			}
-			return
-		}
-		var result = make(map[string]interface{})
-		for _, key := range keys {
-			if f, ok := reg.value(key); ok {
-				// invoke the function for key and store the result
-				result[key] = f()
-			}
-		}
-		if err := c.Encode(result); err != nil {
-			log.Printf("gmx: could not send response to client %v: %v", c.RemoteAddr(), err)
-			return
-		}
-	}
-}
-
-type registry struct {
-	sync.Mutex // protects entries from concurrent mutation
-	entries    map[string]func() interface{}
-}
-
-func (r *registry) register(key string, f func() interface{}) {
-	r.Lock()
-	defer r.Unlock()
-	r.entries[key] = f
-}
-
-func (r *registry) value(key string) (func() interface{}, bool) {
-	r.Lock()
-	defer r.Unlock()
-	f, ok := r.entries[key]
-	return f, ok
-}
-
-func (r *registry) keys() (k []string) {
-	r.Lock()
-	defer r.Unlock()
-	for e := range r.entries {
-		k = append(k, e)
-	}
-	return
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/gmxc/README.md
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/gmxc/README.md b/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/gmxc/README.md
deleted file mode 100644
index 79ead26..0000000
--- a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/gmxc/README.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# gmx client
-
-`gmxc` is a simple command line client for interacting with gmx enabled processes.
-
-## Usage
-
-### Listing processes
-
-The default invocation of `gmxc` will list the accessible gmx enabled processes currently running
-
-	% ./gmxc 
-	.gmx.16207.0    ["./godoc" "-v" "-http=:8080"]
-
-### Retrieving gmx values
-
-	./gmxc -p 16207 runtime.numcpu
-	runtime.numcpu: 4
-
-	./gmxc -p 14968 'runtime.(numcpu|version)'
-	runtime.version: weekly.2012-01-27 11662
-	runtime.numcpu: 2
-
-### Listing all gmx values
-
-	% ./gmxc -p 16207 keys
-	keys: [keys runtime.memstats runtime.gomaxprocs runtime.version os.args runtime.numcpu runtime.cgocalls]
-

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/gmxc/main.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/gmxc/main.go b/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/gmxc/main.go
deleted file mode 100644
index 071c55e..0000000
--- a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/gmxc/main.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package main
-
-import (
-	"encoding/json"
-	"flag"
-	"fmt"
-	"log"
-	"net"
-	"os"
-	"path/filepath"
-	"regexp"
-	"time"
-)
-
-var (
-	delay    = flag.Duration("d", 0, "delay between updates")
-	duration = flag.Duration("D", 0, "duration to output continually")
-
-	pid = flag.Int("p", 0, "process to inspect")
-
-	socketregex = regexp.MustCompile(`\.gmx\.[0-9]+\.0`)
-)
-
-type conn struct {
-	net.Conn
-	*json.Decoder
-	*json.Encoder
-}
-
-func dial(addr string) (*conn, error) {
-	c, err := net.Dial("unix", addr)
-	return &conn{
-		c,
-		json.NewDecoder(c),
-		json.NewEncoder(c),
-	}, err
-}
-
-func listGmxProcesses() {
-	dir, err := os.Open(os.TempDir())
-	if err != nil {
-		log.Fatalf("unable to open %s: %v", os.TempDir(), err)
-	}
-	pids, err := dir.Readdirnames(0)
-	if err != nil {
-		log.Fatalf("unable to read pids: %v", err)
-	}
-	for _, pid := range pids {
-		if socketregex.MatchString(pid) {
-			c, err := dial(filepath.Join(os.TempDir(), pid))
-			if err != nil {
-				continue
-			}
-			defer c.Close()
-			c.Encode([]string{"os.args"})
-			var result = make(map[string]interface{})
-			if err := c.Decode(&result); err != nil {
-				log.Printf("unable to decode response from %s: %v", pid, err)
-				continue
-			}
-			if args, ok := result["os.args"]; ok {
-				fmt.Printf("%s\t%v\n", pid, args)
-			}
-		}
-	}
-}
-
-// fetchKeys returns all the registered keys from the process.
-func fetchKeys(c *conn) []string {
-	// retrieve list of registered keys
-	if err := c.Encode([]string{"keys"}); err != nil {
-		log.Fatalf("unable to send keys request to process: %v", err)
-	}
-	var result = make(map[string][]string)
-	if err := c.Decode(&result); err != nil {
-		log.Fatalf("unable to decode keys response: %v", err)
-	}
-	keys, ok := result["keys"]
-	if !ok {
-		log.Fatalf("gmx server did not return a keys list")
-	}
-	return keys
-}
-
-func main() {
-	flag.Parse()
-	if *pid == 0 {
-		listGmxProcesses()
-		return
-	}
-	c, err := dial(filepath.Join(os.TempDir(), fmt.Sprintf(".gmx.%d.0", *pid)))
-	if err != nil {
-		log.Fatalf("unable to connect to process %d: %v", *pid, err)
-	}
-	defer c.Close()
-
-	// match flag.Args() as regexps
-	registeredKeys := fetchKeys(c)
-	var keys []string
-	for _, a := range flag.Args() {
-		r, err := regexp.Compile(a)
-		if err != nil {
-			log.Fatal("unable to compile regex %v: %v", a, err)
-		}
-		for _, k := range registeredKeys {
-			if r.MatchString(k) {
-				keys = append(keys, k)
-			}
-		}
-	}
-
-	deadline := time.Now().Add(*duration)
-	for {
-		if err := c.Encode(keys); err != nil {
-			log.Fatalf("unable to send request to process: %v", err)
-		}
-		var result = make(map[string]interface{})
-		if err := c.Decode(&result); err != nil {
-			log.Fatalf("unable to decode response: %v", err)
-		}
-		for k, v := range result {
-			fmt.Printf("%s: %v\n", k, v)
-		}
-		if time.Now().After(deadline) {
-			return
-		}
-		time.Sleep(*delay)
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/instrument.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/instrument.go b/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/instrument.go
deleted file mode 100644
index 68c4ca5..0000000
--- a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/instrument.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package gmx
-
-import (
-	"sync/atomic"
-)
-
-type Counter struct {
-	value uint64
-}
-
-func (c *Counter) Inc() {
-	atomic.AddUint64(&c.value, 1)
-}
-
-func NewCounter(name string) *Counter {
-	c := new(Counter)
-	Publish(name, func() interface{} {
-		return c.value
-	})
-	return c
-}
-
-type Gauge struct {
-	value int64
-}
-
-func (g *Gauge) Inc() {
-	atomic.AddInt64(&g.value, 1)
-}
-
-func (g *Gauge) Dec() {
-	atomic.AddInt64(&g.value, -1)
-}
-
-func NewGauge(name string) *Gauge {
-	g := new(Gauge)
-	Publish(name, func() interface{} {
-		return g.value
-	})
-	return g
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/os.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/os.go b/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/os.go
deleted file mode 100644
index 92dd372..0000000
--- a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/os.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package gmx
-
-// pkg/os instrumentation
-
-import (
-	"os"
-)
-
-func init() {
-	Publish("os.args", osArgs)
-}
-
-func osArgs() interface{} {
-	return os.Args
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/runtime.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/runtime.go b/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/runtime.go
deleted file mode 100644
index ada86fd..0000000
--- a/traffic_monitor/experimental/vendor/github.com/davecheney/gmx/runtime.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package gmx
-
-// pkg/runtime instrumentation
-
-import "runtime"
-
-var memstats runtime.MemStats
-
-func init() {
-	Publish("runtime.gomaxprocs", runtimeGOMAXPROCS)
-	Publish("runtime.numcgocall", runtimeNumCgoCall)
-	Publish("runtime.numcpu", runtimeNumCPU)
-	Publish("runtime.numgoroutine", runtimeNumGoroutine)
-	Publish("runtime.version", runtimeVersion)
-
-	Publish("runtime.memstats", runtimeMemStats)
-}
-
-func runtimeGOMAXPROCS() interface{} {
-	return runtime.GOMAXPROCS(0)
-}
-
-func runtimeNumCgoCall() interface{} {
-	return runtime.NumCgoCall()
-}
-
-func runtimeNumCPU() interface{} {
-	return runtime.NumCPU()
-}
-
-func runtimeNumGoroutine() interface{} {
-	return runtime.NumGoroutine()
-}
-
-func runtimeVersion() interface{} {
-	return runtime.Version()
-}
-
-func runtimeMemStats() interface{} {
-	runtime.ReadMemStats(&memstats)
-	return memstats
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/LICENSE
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/LICENSE b/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/LICENSE
deleted file mode 100644
index efcb241..0000000
--- a/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/LICENSE
+++ /dev/null
@@ -1,10 +0,0 @@
-Copyright (c) 2014, Eric Urban
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
-1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-
-2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/README.md
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/README.md b/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/README.md
deleted file mode 100644
index bf19193..0000000
--- a/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-stoppableListener
-=================
-
-An example of a stoppable TCP listener in Go. This library wraps an existing TCP connection object. A goroutine calling `Accept()`
-is interrupted with `StoppedError` whenever the listener is stopped by a call to `Stop()`. Usage is demonstrated below, and in `example/example.go`.
-
-
-```
-	originalListener, err := net.Listen("tcp", ":8080")
-	if err != nil {
-		panic(err)
-	}
-
-	sl, err := stoppableListener.New(originalListener)
-	if err != nil {
-		panic(err)
-	}
-```

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/example/example.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/example/example.go b/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/example/example.go
deleted file mode 100644
index 3c8b323..0000000
--- a/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/example/example.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package main
-
-import (
-	"fmt"
-	"github.com/hydrogen18/stoppableListener"
-	"net"
-	"net/http"
-	"os"
-	"os/signal"
-	"sync"
-	"syscall"
-)
-
-func helloHttp(rw http.ResponseWriter, req *http.Request) {
-	rw.WriteHeader(http.StatusOK)
-	fmt.Fprintf(rw, "Hello HTTP!\n")
-}
-
-func main() {
-	originalListener, err := net.Listen("tcp", ":8080")
-	if err != nil {
-		panic(err)
-	}
-
-	sl, err := stoppableListener.New(originalListener)
-	if err != nil {
-		panic(err)
-	}
-
-	http.HandleFunc("/", helloHttp)
-	server := http.Server{}
-
-	stop := make(chan os.Signal)
-	signal.Notify(stop, syscall.SIGINT)
-	var wg sync.WaitGroup
-	wg.Add(1)
-	go func() {
-		defer wg.Done()
-		server.Serve(sl)
-	}()
-
-	fmt.Printf("Serving HTTP\n")
-	select {
-	case signal := <-stop:
-		fmt.Printf("Got signal:%v\n", signal)
-	}
-	fmt.Printf("Stopping listener\n")
-	sl.Stop()
-	fmt.Printf("Waiting on server\n")
-	wg.Wait()
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/listener.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/listener.go b/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/listener.go
deleted file mode 100644
index 69a9f33..0000000
--- a/traffic_monitor/experimental/vendor/github.com/hydrogen18/stoppableListener/listener.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package stoppableListener
-
-import (
-	"errors"
-	"net"
-	"time"
-)
-
-type StoppableListener struct {
-	*net.TCPListener          //Wrapped listener
-	stop             chan int //Channel used only to indicate listener should shutdown
-}
-
-func New(l net.Listener) (*StoppableListener, error) {
-	tcpL, ok := l.(*net.TCPListener)
-
-	if !ok {
-		return nil, errors.New("Cannot wrap listener")
-	}
-
-	retval := &StoppableListener{}
-	retval.TCPListener = tcpL
-	retval.stop = make(chan int)
-
-	return retval, nil
-}
-
-var StoppedError = errors.New("Listener stopped")
-
-func (sl *StoppableListener) Accept() (net.Conn, error) {
-
-	for {
-		//Wait up to one second for a new connection
-		sl.SetDeadline(time.Now().Add(time.Second))
-
-		newConn, err := sl.TCPListener.Accept()
-
-		//Check for the channel being closed
-		select {
-		case <-sl.stop:
-			return nil, StoppedError
-		default:
-			//If the channel is still open, continue as normal
-		}
-
-		if err != nil {
-			netErr, ok := err.(net.Error)
-
-			//If this is a timeout, then continue to wait for
-			//new connections
-			if ok && netErr.Timeout() && netErr.Temporary() {
-				continue
-			}
-		}
-
-		return newConn, err
-	}
-}
-
-func (sl *StoppableListener) Stop() {
-	close(sl.stop)
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/.gitignore
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/.gitignore b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/.gitignore
deleted file mode 100644
index 4cd0cba..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-# Setup a Global .gitignore for OS and editor generated files:
-# https://help.github.com/articles/ignoring-files
-# git config --global core.excludesfile ~/.gitignore_global
-
-.vagrant
-*.sublime-project

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/.travis.yml
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/.travis.yml b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/.travis.yml
deleted file mode 100644
index 00fd5dd..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/.travis.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-sudo: false
-language: go
-
-go:
-  - 1.5.4
-  - 1.6.1
-  - tip
-
-matrix:
-  allow_failures:
-    - go: tip
-
-before_script:
-  - go get -u github.com/golang/lint/golint
-
-script:
-  - go test -v --race ./...
-
-after_script:
-  - test -z "$(gofmt -s -l -w . | tee /dev/stderr)"
-  - test -z "$(golint ./...     | tee /dev/stderr)"
-  - go vet ./...
-
-os:
-  - linux
-  - osx
-
-notifications:
-  email: false

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/AUTHORS
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/AUTHORS b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/AUTHORS
deleted file mode 100644
index 6438bb3..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/AUTHORS
+++ /dev/null
@@ -1,43 +0,0 @@
-# Names should be added to this file as
-#	Name or Organization <email address>
-# The email address is not required for organizations.
-
-# You can update this list using the following command:
-#
-#   $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
-
-# Please keep the list sorted.
-
-Adrien Bustany <ad...@bustany.org>
-Amit Krishnan <am...@oracle.com>
-Bj�rn Erik Pedersen <bj...@gmail.com>
-Caleb Spare <ce...@gmail.com>
-Case Nelson <ca...@teammating.com>
-Chris Howey <ch...@howey.me> <ho...@gmail.com>
-Christoffer Buchholz <ch...@gmail.com>
-Daniel Wagner-Hall <da...@gmail.com>
-Dave Cheney <da...@cheney.net>
-Evan Phoenix <ev...@fallingsnow.net>
-Francisco Souza <f...@souza.cc>
-Hari haran <ha...@gmail.com>
-John C Barstow
-Kelvin Fo <vm...@gmail.com>
-Ken-ichirou MATSUZAWA <ch...@h4.dion.ne.jp>
-Matt Layher <md...@gmail.com>
-Nathan Youngman <gi...@nathany.com>
-Paul Hammond <pa...@paulhammond.org>
-Pawel Knap <pa...@gmail.com>
-Pieter Droogendijk <pi...@binky.org.uk>
-Pursuit92 <Jo...@techpursuit.net>
-Riku Voipio <ri...@linaro.org>
-Rob Figueiredo <ro...@gmail.com>
-Soge Zhang <zh...@gmail.com>
-Tiffany Jernigan <ti...@intel.com>
-Tilak Sharma <ti...@google.com>
-Travis Cline <tr...@gmail.com>
-Tudor Golubenco <tu...@gmail.com>
-Yukang <mo...@gmail.com>
-bronze1man <br...@gmail.com>
-debrando <de...@gmail.com>
-henrikedwards <he...@gmail.com>
-\u94c1\u54e5 <gu...@gmail.com>

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/CHANGELOG.md
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/CHANGELOG.md b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/CHANGELOG.md
deleted file mode 100644
index 675fab9..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/CHANGELOG.md
+++ /dev/null
@@ -1,291 +0,0 @@
-# Changelog
-
-## v1.3.0 / 2016-04-19
-
-* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
-
-## v1.2.10 / 2016-03-02
-
-* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
-
-## v1.2.9 / 2016-01-13
-
-kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
-
-## v1.2.8 / 2015-12-17
-
-* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
-* inotify: fix race in test
-* enable race detection for continuous integration (Linux, Mac, Windows)
-
-## v1.2.5 / 2015-10-17
-
-* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
-* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
-* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
-* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
-
-## v1.2.1 / 2015-10-14
-
-* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
-
-## v1.2.0 / 2015-02-08
-
-* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
-* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
-* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
-
-## v1.1.1 / 2015-02-05
-
-* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
-
-## v1.1.0 / 2014-12-12
-
-* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
-    * add low-level functions
-    * only need to store flags on directories
-    * less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
-    * done can be an unbuffered channel
-    * remove calls to os.NewSyscallError
-* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
-* kqueue: fix regression in  rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
-* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
-
-## v1.0.4 / 2014-09-07
-
-* kqueue: add dragonfly to the build tags.
-* Rename source code files, rearrange code so exported APIs are at the top.
-* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
-
-## v1.0.3 / 2014-08-19
-
-* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
-
-## v1.0.2 / 2014-08-17
-
-* [Fix] Missing create events on OS X. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
-* [Fix] Make ./path and path equivalent. (thanks @zhsso)
-
-## v1.0.0 / 2014-08-15
-
-* [API] Remove AddWatch on Windows, use Add.
-* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
-* Minor updates based on feedback from golint.
-
-## dev / 2014-07-09
-
-* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
-* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
-
-## dev / 2014-07-04
-
-* kqueue: fix incorrect mutex used in Close()
-* Update example to demonstrate usage of Op.
-
-## dev / 2014-06-28
-
-* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
-* Fix for String() method on Event (thanks Alex Brainman)
-* Don't build on Plan 9 or Solaris (thanks @4ad)
-
-## dev / 2014-06-21
-
-* Events channel of type Event rather than *Event.
-* [internal] use syscall constants directly for inotify and kqueue.
-* [internal] kqueue: rename events to kevents and fileEvent to event.
-
-## dev / 2014-06-19
-
-* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
-* [internal] remove cookie from Event struct (unused).
-* [internal] Event struct has the same definition across every OS.
-* [internal] remove internal watch and removeWatch methods.
-
-## dev / 2014-06-12
-
-* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
-* [API] Pluralized channel names: Events and Errors.
-* [API] Renamed FileEvent struct to Event.
-* [API] Op constants replace methods like IsCreate().
-
-## dev / 2014-06-12
-
-* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
-
-## dev / 2014-05-23
-
-* [API] Remove current implementation of WatchFlags.
-    * current implementation doesn't take advantage of OS for efficiency
-    * provides little benefit over filtering events as they are received, but has  extra bookkeeping and mutexes
-    * no tests for the current implementation
-    * not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
-
-## v0.9.3 / 2014-12-31
-
-* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
-
-## v0.9.2 / 2014-08-17
-
-* [Backport] Fix missing create events on OS X. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
-
-## v0.9.1 / 2014-06-12
-
-* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
-
-## v0.9.0 / 2014-01-17
-
-* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
-* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
-* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
-
-## v0.8.12 / 2013-11-13
-
-* [API] Remove FD_SET and friends from Linux adapter
-
-## v0.8.11 / 2013-11-02
-
-* [Doc] Add Changelog [#72][] (thanks @nathany)
-* [Doc] Spotlight and double modify events on OS X [#62][] (reported by @paulhammond)
-
-## v0.8.10 / 2013-10-19
-
-* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
-* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
-* [Doc] specify OS-specific limits in README (thanks @debrando)
-
-## v0.8.9 / 2013-09-08
-
-* [Doc] Contributing (thanks @nathany)
-* [Doc] update package path in example code [#63][] (thanks @paulhammond)
-* [Doc] GoCI badge in README (Linux only) [#60][]
-* [Doc] Cross-platform testing with Vagrant  [#59][] (thanks @nathany)
-
-## v0.8.8 / 2013-06-17
-
-* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
-
-## v0.8.7 / 2013-06-03
-
-* [API] Make syscall flags internal
-* [Fix] inotify: ignore event changes
-* [Fix] race in symlink test [#45][] (reported by @srid)
-* [Fix] tests on Windows
-* lower case error messages
-
-## v0.8.6 / 2013-05-23
-
-* kqueue: Use EVT_ONLY flag on Darwin
-* [Doc] Update README with full example
-
-## v0.8.5 / 2013-05-09
-
-* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
-
-## v0.8.4 / 2013-04-07
-
-* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
-
-## v0.8.3 / 2013-03-13
-
-* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
-* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
-
-## v0.8.2 / 2013-02-07
-
-* [Doc] add Authors
-* [Fix] fix data races for map access [#29][] (thanks @fsouza)
-
-## v0.8.1 / 2013-01-09
-
-* [Fix] Windows path separators
-* [Doc] BSD License
-
-## v0.8.0 / 2012-11-09
-
-* kqueue: directory watching improvements (thanks @vmirage)
-* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
-* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
-
-## v0.7.4 / 2012-10-09
-
-* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
-* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
-* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
-* [Fix] kqueue: modify after recreation of file
-
-## v0.7.3 / 2012-09-27
-
-* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
-* [Fix] kqueue: no longer get duplicate CREATE events
-
-## v0.7.2 / 2012-09-01
-
-* kqueue: events for created directories
-
-## v0.7.1 / 2012-07-14
-
-* [Fix] for renaming files
-
-## v0.7.0 / 2012-07-02
-
-* [Feature] FSNotify flags
-* [Fix] inotify: Added file name back to event path
-
-## v0.6.0 / 2012-06-06
-
-* kqueue: watch files after directory created (thanks @tmc)
-
-## v0.5.1 / 2012-05-22
-
-* [Fix] inotify: remove all watches before Close()
-
-## v0.5.0 / 2012-05-03
-
-* [API] kqueue: return errors during watch instead of sending over channel
-* kqueue: match symlink behavior on Linux
-* inotify: add `DELETE_SELF` (requested by @taralx)
-* [Fix] kqueue: handle EINTR (reported by @robfig)
-* [Doc] Godoc example [#1][] (thanks @davecheney)
-
-## v0.4.0 / 2012-03-30
-
-* Go 1 released: build with go tool
-* [Feature] Windows support using winfsnotify
-* Windows does not have attribute change notifications
-* Roll attribute notifications into IsModify
-
-## v0.3.0 / 2012-02-19
-
-* kqueue: add files when watch directory
-
-## v0.2.0 / 2011-12-30
-
-* update to latest Go weekly code
-
-## v0.1.0 / 2011-10-19
-
-* kqueue: add watch on file creation to match inotify
-* kqueue: create file event
-* inotify: ignore `IN_IGNORED` events
-* event String()
-* linux: common FileEvent functions
-* initial commit
-
-[#79]: https://github.com/howeyc/fsnotify/pull/79
-[#77]: https://github.com/howeyc/fsnotify/pull/77
-[#72]: https://github.com/howeyc/fsnotify/issues/72
-[#71]: https://github.com/howeyc/fsnotify/issues/71
-[#70]: https://github.com/howeyc/fsnotify/issues/70
-[#63]: https://github.com/howeyc/fsnotify/issues/63
-[#62]: https://github.com/howeyc/fsnotify/issues/62
-[#60]: https://github.com/howeyc/fsnotify/issues/60
-[#59]: https://github.com/howeyc/fsnotify/issues/59
-[#49]: https://github.com/howeyc/fsnotify/issues/49
-[#45]: https://github.com/howeyc/fsnotify/issues/45
-[#40]: https://github.com/howeyc/fsnotify/issues/40
-[#36]: https://github.com/howeyc/fsnotify/issues/36
-[#33]: https://github.com/howeyc/fsnotify/issues/33
-[#29]: https://github.com/howeyc/fsnotify/issues/29
-[#25]: https://github.com/howeyc/fsnotify/issues/25
-[#24]: https://github.com/howeyc/fsnotify/issues/24
-[#21]: https://github.com/howeyc/fsnotify/issues/21

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/CONTRIBUTING.md
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/CONTRIBUTING.md b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/CONTRIBUTING.md
deleted file mode 100644
index 617e45a..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/CONTRIBUTING.md
+++ /dev/null
@@ -1,77 +0,0 @@
-# Contributing
-
-## Issues
-
-* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues).
-* Please indicate the platform you are using fsnotify on.
-* A code example to reproduce the problem is appreciated.
-
-## Pull Requests
-
-### Contributor License Agreement
-
-fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
-
-Please indicate that you have signed the CLA in your pull request.
-
-### How fsnotify is Developed
-
-* Development is done on feature branches.
-* Tests are run on BSD, Linux, OS X and Windows.
-* Pull requests are reviewed and [applied to master][am] using [hub][].
-  * Maintainers may modify or squash commits rather than asking contributors to.
-* To issue a new release, the maintainers will:
-  * Update the CHANGELOG
-  * Tag a version, which will become available through gopkg.in.
- 
-### How to Fork
-
-For smooth sailing, always use the original import path. Installing with `go get` makes this easy. 
-
-1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`)
-2. Create your feature branch (`git checkout -b my-new-feature`)
-3. Ensure everything works and the tests pass (see below)
-4. Commit your changes (`git commit -am 'Add some feature'`)
-
-Contribute upstream:
-
-1. Fork fsnotify on GitHub
-2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
-3. Push to the branch (`git push fork my-new-feature`)
-4. Create a new Pull Request on GitHub
-
-This workflow is [thoroughly explained by Katrina Owen](https://blog.splice.com/contributing-open-source-git-repositories-go/).
-
-### Testing
-
-fsnotify uses build tags to compile different code on Linux, BSD, OS X, and Windows.
-
-Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
-
-To aid in cross-platform testing there is a Vagrantfile for Linux and BSD.
-
-* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/)
-* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder.
-* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
-* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`.
-* When you're done, you will want to halt or destroy the Vagrant boxes.
-
-Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory.
-
-Right now there is no equivalent solution for Windows and OS X, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads).
-
-### Maintainers
-
-Help maintaining fsnotify is welcome. To be a maintainer:
-
-* Submit a pull request and sign the CLA as above.
-* You must be able to run the test suite on Mac, Windows, Linux and BSD.
-
-To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][].
-
-All code changes should be internal pull requests.
-
-Releases are tagged using [Semantic Versioning](http://semver.org/).
-
-[hub]: https://github.com/github/hub
-[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/LICENSE
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/LICENSE b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/LICENSE
deleted file mode 100644
index f21e540..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/LICENSE
+++ /dev/null
@@ -1,28 +0,0 @@
-Copyright (c) 2012 The Go Authors. All rights reserved.
-Copyright (c) 2012 fsnotify Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-   * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
-   * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
-   * Neither the name of Google Inc. nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/README.md
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/README.md b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/README.md
deleted file mode 100644
index 5ebce86..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/README.md
+++ /dev/null
@@ -1,50 +0,0 @@
-# File system notifications for Go
-
-[![GoDoc](https://godoc.org/github.com/fsnotify/fsnotify?status.svg)](https://godoc.org/github.com/fsnotify/fsnotify) [![Go Report Card](https://goreportcard.com/badge/github.com/fsnotify/fsnotify)](https://goreportcard.com/report/github.com/fsnotify/fsnotify) [![Coverage](http://gocover.io/_badge/github.com/fsnotify/fsnotify)](http://gocover.io/github.com/fsnotify/fsnotify) 
-
-fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library. Ensure you have the latest version installed by running:
-
-```console
-go get -u golang.org/x/sys/...
-```
-
-Cross platform: Windows, Linux, BSD and OS X.
-
-|Adapter   |OS        |Status    |
-|----------|----------|----------|
-|inotify   |Linux 2.6.27 or later, Android\*|Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify)|
-|kqueue    |BSD, OS X, iOS\*|Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify)|
-|ReadDirectoryChangesW|Windows|Supported [![Build status](https://ci.appveyor.com/api/projects/status/ivwjubaih4r0udeh/branch/master?svg=true)](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)|
-|FSEvents  |OS X          |[Planned](https://github.com/fsnotify/fsnotify/issues/11)|
-|FEN       |Solaris 11    |[In Progress](https://github.com/fsnotify/fsnotify/issues/12)|
-|fanotify  |Linux 2.6.37+ | |
-|USN Journals |Windows    |[Maybe](https://github.com/fsnotify/fsnotify/issues/53)|
-|Polling   |*All*         |[Maybe](https://github.com/fsnotify/fsnotify/issues/9)|
-
-\* Android and iOS are untested.
-
-Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) for usage. Consult the [Wiki](https://github.com/fsnotify/fsnotify/wiki) for the FAQ and further information.
-
-## API stability
-
-fsnotify is a fork of [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA). 
-
-All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/). Further API changes are [planned](https://github.com/fsnotify/fsnotify/milestones), and will be tagged with a new major revision number.
-
-Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`.
-
-## Contributing
-
-Please refer to [CONTRIBUTING][] before opening an issue or pull request.
-
-## Example
-
-See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go).
-
-[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md
-
-## Related Projects
-
-* [notify](https://github.com/rjeczalik/notify)
-* [fsevents](https://github.com/fsnotify/fsevents)
-

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/example_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/example_test.go b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/example_test.go
deleted file mode 100644
index 700502c..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/example_test.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2012 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build !plan9
-
-package fsnotify_test
-
-import (
-	"log"
-
-	"github.com/fsnotify/fsnotify"
-)
-
-func ExampleNewWatcher() {
-	watcher, err := fsnotify.NewWatcher()
-	if err != nil {
-		log.Fatal(err)
-	}
-	defer watcher.Close()
-
-	done := make(chan bool)
-	go func() {
-		for {
-			select {
-			case event := <-watcher.Events:
-				log.Println("event:", event)
-				if event.Op&fsnotify.Write == fsnotify.Write {
-					log.Println("modified file:", event.Name)
-				}
-			case err := <-watcher.Errors:
-				log.Println("error:", err)
-			}
-		}
-	}()
-
-	err = watcher.Add("/tmp/foo")
-	if err != nil {
-		log.Fatal(err)
-	}
-	<-done
-}



[11/19] incubator-trafficcontrol git commit: Move TM2 to trafficcontrol/traffic_monitor_golang

Posted by ne...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/fen.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/fen.go b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/fen.go
deleted file mode 100644
index ced39cb..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/fen.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2010 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build solaris
-
-package fsnotify
-
-import (
-	"errors"
-)
-
-// Watcher watches a set of files, delivering events to a channel.
-type Watcher struct {
-	Events chan Event
-	Errors chan error
-}
-
-// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
-func NewWatcher() (*Watcher, error) {
-	return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
-}
-
-// Close removes all watches and closes the events channel.
-func (w *Watcher) Close() error {
-	return nil
-}
-
-// Add starts watching the named file or directory (non-recursively).
-func (w *Watcher) Add(name string) error {
-	return nil
-}
-
-// Remove stops watching the the named file or directory (non-recursively).
-func (w *Watcher) Remove(name string) error {
-	return nil
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/fsnotify.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/fsnotify.go b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/fsnotify.go
deleted file mode 100644
index d1d39a0..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/fsnotify.go
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2012 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build !plan9
-
-// Package fsnotify provides a platform-independent interface for file system notifications.
-package fsnotify
-
-import (
-	"bytes"
-	"fmt"
-)
-
-// Event represents a single file system notification.
-type Event struct {
-	Name string // Relative path to the file or directory.
-	Op   Op     // File operation that triggered the event.
-}
-
-// Op describes a set of file operations.
-type Op uint32
-
-// These are the generalized file operations that can trigger a notification.
-const (
-	Create Op = 1 << iota
-	Write
-	Remove
-	Rename
-	Chmod
-)
-
-// String returns a string representation of the event in the form
-// "file: REMOVE|WRITE|..."
-func (e Event) String() string {
-	// Use a buffer for efficient string concatenation
-	var buffer bytes.Buffer
-
-	if e.Op&Create == Create {
-		buffer.WriteString("|CREATE")
-	}
-	if e.Op&Remove == Remove {
-		buffer.WriteString("|REMOVE")
-	}
-	if e.Op&Write == Write {
-		buffer.WriteString("|WRITE")
-	}
-	if e.Op&Rename == Rename {
-		buffer.WriteString("|RENAME")
-	}
-	if e.Op&Chmod == Chmod {
-		buffer.WriteString("|CHMOD")
-	}
-
-	// If buffer remains empty, return no event names
-	if buffer.Len() == 0 {
-		return fmt.Sprintf("%q: ", e.Name)
-	}
-
-	// Return a list of event names, with leading pipe character stripped
-	return fmt.Sprintf("%q: %s", e.Name, buffer.String()[1:])
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify.go b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify.go
deleted file mode 100644
index 9700df5..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify.go
+++ /dev/null
@@ -1,325 +0,0 @@
-// Copyright 2010 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build linux
-
-package fsnotify
-
-import (
-	"errors"
-	"fmt"
-	"io"
-	"os"
-	"path/filepath"
-	"strings"
-	"sync"
-	"unsafe"
-
-	"golang.org/x/sys/unix"
-)
-
-// Watcher watches a set of files, delivering events to a channel.
-type Watcher struct {
-	Events   chan Event
-	Errors   chan error
-	mu       sync.Mutex // Map access
-	cv       *sync.Cond // sync removing on rm_watch with IN_IGNORE
-	fd       int
-	poller   *fdPoller
-	watches  map[string]*watch // Map of inotify watches (key: path)
-	paths    map[int]string    // Map of watched paths (key: watch descriptor)
-	done     chan struct{}     // Channel for sending a "quit message" to the reader goroutine
-	doneResp chan struct{}     // Channel to respond to Close
-}
-
-// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
-func NewWatcher() (*Watcher, error) {
-	// Create inotify fd
-	fd, errno := unix.InotifyInit()
-	if fd == -1 {
-		return nil, errno
-	}
-	// Create epoll
-	poller, err := newFdPoller(fd)
-	if err != nil {
-		unix.Close(fd)
-		return nil, err
-	}
-	w := &Watcher{
-		fd:       fd,
-		poller:   poller,
-		watches:  make(map[string]*watch),
-		paths:    make(map[int]string),
-		Events:   make(chan Event),
-		Errors:   make(chan error),
-		done:     make(chan struct{}),
-		doneResp: make(chan struct{}),
-	}
-	w.cv = sync.NewCond(&w.mu)
-
-	go w.readEvents()
-	return w, nil
-}
-
-func (w *Watcher) isClosed() bool {
-	select {
-	case <-w.done:
-		return true
-	default:
-		return false
-	}
-}
-
-// Close removes all watches and closes the events channel.
-func (w *Watcher) Close() error {
-	if w.isClosed() {
-		return nil
-	}
-
-	// Send 'close' signal to goroutine, and set the Watcher to closed.
-	close(w.done)
-
-	// Wake up goroutine
-	w.poller.wake()
-
-	// Wait for goroutine to close
-	<-w.doneResp
-
-	return nil
-}
-
-// Add starts watching the named file or directory (non-recursively).
-func (w *Watcher) Add(name string) error {
-	name = filepath.Clean(name)
-	if w.isClosed() {
-		return errors.New("inotify instance already closed")
-	}
-
-	const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
-		unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
-		unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
-
-	var flags uint32 = agnosticEvents
-
-	w.mu.Lock()
-	watchEntry, found := w.watches[name]
-	w.mu.Unlock()
-	if found {
-		watchEntry.flags |= flags
-		flags |= unix.IN_MASK_ADD
-	}
-	wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
-	if wd == -1 {
-		return errno
-	}
-
-	w.mu.Lock()
-	w.watches[name] = &watch{wd: uint32(wd), flags: flags}
-	w.paths[wd] = name
-	w.mu.Unlock()
-
-	return nil
-}
-
-// Remove stops watching the named file or directory (non-recursively).
-func (w *Watcher) Remove(name string) error {
-	name = filepath.Clean(name)
-
-	// Fetch the watch.
-	w.mu.Lock()
-	defer w.mu.Unlock()
-	watch, ok := w.watches[name]
-
-	// Remove it from inotify.
-	if !ok {
-		return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
-	}
-	// inotify_rm_watch will return EINVAL if the file has been deleted;
-	// the inotify will already have been removed.
-	// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
-	// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
-	// so that EINVAL means that the wd is being rm_watch()ed or its file removed
-	// by another thread and we have not received IN_IGNORE event.
-	success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
-	if success == -1 {
-		// TODO: Perhaps it's not helpful to return an error here in every case.
-		// the only two possible errors are:
-		// EBADF, which happens when w.fd is not a valid file descriptor of any kind.
-		// EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor.
-		// Watch descriptors are invalidated when they are removed explicitly or implicitly;
-		// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted.
-		return errno
-	}
-
-	// wait until ignoreLinux() deleting maps
-	exists := true
-	for exists {
-		w.cv.Wait()
-		_, exists = w.watches[name]
-	}
-
-	return nil
-}
-
-type watch struct {
-	wd    uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
-	flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
-}
-
-// readEvents reads from the inotify file descriptor, converts the
-// received events into Event objects and sends them via the Events channel
-func (w *Watcher) readEvents() {
-	var (
-		buf   [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
-		n     int                                  // Number of bytes read with read()
-		errno error                                // Syscall errno
-		ok    bool                                 // For poller.wait
-	)
-
-	defer close(w.doneResp)
-	defer close(w.Errors)
-	defer close(w.Events)
-	defer unix.Close(w.fd)
-	defer w.poller.close()
-
-	for {
-		// See if we have been closed.
-		if w.isClosed() {
-			return
-		}
-
-		ok, errno = w.poller.wait()
-		if errno != nil {
-			select {
-			case w.Errors <- errno:
-			case <-w.done:
-				return
-			}
-			continue
-		}
-
-		if !ok {
-			continue
-		}
-
-		n, errno = unix.Read(w.fd, buf[:])
-		// If a signal interrupted execution, see if we've been asked to close, and try again.
-		// http://man7.org/linux/man-pages/man7/signal.7.html :
-		// "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable"
-		if errno == unix.EINTR {
-			continue
-		}
-
-		// unix.Read might have been woken up by Close. If so, we're done.
-		if w.isClosed() {
-			return
-		}
-
-		if n < unix.SizeofInotifyEvent {
-			var err error
-			if n == 0 {
-				// If EOF is received. This should really never happen.
-				err = io.EOF
-			} else if n < 0 {
-				// If an error occurred while reading.
-				err = errno
-			} else {
-				// Read was too short.
-				err = errors.New("notify: short read in readEvents()")
-			}
-			select {
-			case w.Errors <- err:
-			case <-w.done:
-				return
-			}
-			continue
-		}
-
-		var offset uint32
-		// We don't know how many events we just read into the buffer
-		// While the offset points to at least one whole event...
-		for offset <= uint32(n-unix.SizeofInotifyEvent) {
-			// Point "raw" to the event in the buffer
-			raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
-
-			mask := uint32(raw.Mask)
-			nameLen := uint32(raw.Len)
-			// If the event happened to the watched directory or the watched file, the kernel
-			// doesn't append the filename to the event, but we would like to always fill the
-			// the "Name" field with a valid filename. We retrieve the path of the watch from
-			// the "paths" map.
-			w.mu.Lock()
-			name := w.paths[int(raw.Wd)]
-			w.mu.Unlock()
-			if nameLen > 0 {
-				// Point "bytes" at the first byte of the filename
-				bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))
-				// The filename is padded with NULL bytes. TrimRight() gets rid of those.
-				name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
-			}
-
-			event := newEvent(name, mask)
-
-			// Send the events that are not ignored on the events channel
-			if !event.ignoreLinux(w, raw.Wd, mask) {
-				select {
-				case w.Events <- event:
-				case <-w.done:
-					return
-				}
-			}
-
-			// Move to the next event in the buffer
-			offset += unix.SizeofInotifyEvent + nameLen
-		}
-	}
-}
-
-// Certain types of events can be "ignored" and not sent over the Events
-// channel. Such as events marked ignore by the kernel, or MODIFY events
-// against files that do not exist.
-func (e *Event) ignoreLinux(w *Watcher, wd int32, mask uint32) bool {
-	// Ignore anything the inotify API says to ignore
-	if mask&unix.IN_IGNORED == unix.IN_IGNORED {
-		w.mu.Lock()
-		defer w.mu.Unlock()
-		name := w.paths[int(wd)]
-		delete(w.paths, int(wd))
-		delete(w.watches, name)
-		w.cv.Broadcast()
-		return true
-	}
-
-	// If the event is not a DELETE or RENAME, the file must exist.
-	// Otherwise the event is ignored.
-	// *Note*: this was put in place because it was seen that a MODIFY
-	// event was sent after the DELETE. This ignores that MODIFY and
-	// assumes a DELETE will come or has come if the file doesn't exist.
-	if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
-		_, statErr := os.Lstat(e.Name)
-		return os.IsNotExist(statErr)
-	}
-	return false
-}
-
-// newEvent returns an platform-independent Event based on an inotify mask.
-func newEvent(name string, mask uint32) Event {
-	e := Event{Name: name}
-	if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
-		e.Op |= Create
-	}
-	if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
-		e.Op |= Remove
-	}
-	if mask&unix.IN_MODIFY == unix.IN_MODIFY {
-		e.Op |= Write
-	}
-	if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
-		e.Op |= Rename
-	}
-	if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
-		e.Op |= Chmod
-	}
-	return e
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify_poller.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify_poller.go b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify_poller.go
deleted file mode 100644
index cc7db4b..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify_poller.go
+++ /dev/null
@@ -1,187 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build linux
-
-package fsnotify
-
-import (
-	"errors"
-
-	"golang.org/x/sys/unix"
-)
-
-type fdPoller struct {
-	fd   int    // File descriptor (as returned by the inotify_init() syscall)
-	epfd int    // Epoll file descriptor
-	pipe [2]int // Pipe for waking up
-}
-
-func emptyPoller(fd int) *fdPoller {
-	poller := new(fdPoller)
-	poller.fd = fd
-	poller.epfd = -1
-	poller.pipe[0] = -1
-	poller.pipe[1] = -1
-	return poller
-}
-
-// Create a new inotify poller.
-// This creates an inotify handler, and an epoll handler.
-func newFdPoller(fd int) (*fdPoller, error) {
-	var errno error
-	poller := emptyPoller(fd)
-	defer func() {
-		if errno != nil {
-			poller.close()
-		}
-	}()
-	poller.fd = fd
-
-	// Create epoll fd
-	poller.epfd, errno = unix.EpollCreate1(0)
-	if poller.epfd == -1 {
-		return nil, errno
-	}
-	// Create pipe; pipe[0] is the read end, pipe[1] the write end.
-	errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK)
-	if errno != nil {
-		return nil, errno
-	}
-
-	// Register inotify fd with epoll
-	event := unix.EpollEvent{
-		Fd:     int32(poller.fd),
-		Events: unix.EPOLLIN,
-	}
-	errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event)
-	if errno != nil {
-		return nil, errno
-	}
-
-	// Register pipe fd with epoll
-	event = unix.EpollEvent{
-		Fd:     int32(poller.pipe[0]),
-		Events: unix.EPOLLIN,
-	}
-	errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event)
-	if errno != nil {
-		return nil, errno
-	}
-
-	return poller, nil
-}
-
-// Wait using epoll.
-// Returns true if something is ready to be read,
-// false if there is not.
-func (poller *fdPoller) wait() (bool, error) {
-	// 3 possible events per fd, and 2 fds, makes a maximum of 6 events.
-	// I don't know whether epoll_wait returns the number of events returned,
-	// or the total number of events ready.
-	// I decided to catch both by making the buffer one larger than the maximum.
-	events := make([]unix.EpollEvent, 7)
-	for {
-		n, errno := unix.EpollWait(poller.epfd, events, -1)
-		if n == -1 {
-			if errno == unix.EINTR {
-				continue
-			}
-			return false, errno
-		}
-		if n == 0 {
-			// If there are no events, try again.
-			continue
-		}
-		if n > 6 {
-			// This should never happen. More events were returned than should be possible.
-			return false, errors.New("epoll_wait returned more events than I know what to do with")
-		}
-		ready := events[:n]
-		epollhup := false
-		epollerr := false
-		epollin := false
-		for _, event := range ready {
-			if event.Fd == int32(poller.fd) {
-				if event.Events&unix.EPOLLHUP != 0 {
-					// This should not happen, but if it does, treat it as a wakeup.
-					epollhup = true
-				}
-				if event.Events&unix.EPOLLERR != 0 {
-					// If an error is waiting on the file descriptor, we should pretend
-					// something is ready to read, and let unix.Read pick up the error.
-					epollerr = true
-				}
-				if event.Events&unix.EPOLLIN != 0 {
-					// There is data to read.
-					epollin = true
-				}
-			}
-			if event.Fd == int32(poller.pipe[0]) {
-				if event.Events&unix.EPOLLHUP != 0 {
-					// Write pipe descriptor was closed, by us. This means we're closing down the
-					// watcher, and we should wake up.
-				}
-				if event.Events&unix.EPOLLERR != 0 {
-					// If an error is waiting on the pipe file descriptor.
-					// This is an absolute mystery, and should never ever happen.
-					return false, errors.New("Error on the pipe descriptor.")
-				}
-				if event.Events&unix.EPOLLIN != 0 {
-					// This is a regular wakeup, so we have to clear the buffer.
-					err := poller.clearWake()
-					if err != nil {
-						return false, err
-					}
-				}
-			}
-		}
-
-		if epollhup || epollerr || epollin {
-			return true, nil
-		}
-		return false, nil
-	}
-}
-
-// Close the write end of the poller.
-func (poller *fdPoller) wake() error {
-	buf := make([]byte, 1)
-	n, errno := unix.Write(poller.pipe[1], buf)
-	if n == -1 {
-		if errno == unix.EAGAIN {
-			// Buffer is full, poller will wake.
-			return nil
-		}
-		return errno
-	}
-	return nil
-}
-
-func (poller *fdPoller) clearWake() error {
-	// You have to be woken up a LOT in order to get to 100!
-	buf := make([]byte, 100)
-	n, errno := unix.Read(poller.pipe[0], buf)
-	if n == -1 {
-		if errno == unix.EAGAIN {
-			// Buffer is empty, someone else cleared our wake.
-			return nil
-		}
-		return errno
-	}
-	return nil
-}
-
-// Close all poller file descriptors, but not the one passed to it.
-func (poller *fdPoller) close() {
-	if poller.pipe[1] != -1 {
-		unix.Close(poller.pipe[1])
-	}
-	if poller.pipe[0] != -1 {
-		unix.Close(poller.pipe[0])
-	}
-	if poller.epfd != -1 {
-		unix.Close(poller.epfd)
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify_poller_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify_poller_test.go b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify_poller_test.go
deleted file mode 100644
index 26623ef..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify_poller_test.go
+++ /dev/null
@@ -1,229 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build linux
-
-package fsnotify
-
-import (
-	"testing"
-	"time"
-
-	"golang.org/x/sys/unix"
-)
-
-type testFd [2]int
-
-func makeTestFd(t *testing.T) testFd {
-	var tfd testFd
-	errno := unix.Pipe(tfd[:])
-	if errno != nil {
-		t.Fatalf("Failed to create pipe: %v", errno)
-	}
-	return tfd
-}
-
-func (tfd testFd) fd() int {
-	return tfd[0]
-}
-
-func (tfd testFd) closeWrite(t *testing.T) {
-	errno := unix.Close(tfd[1])
-	if errno != nil {
-		t.Fatalf("Failed to close write end of pipe: %v", errno)
-	}
-}
-
-func (tfd testFd) put(t *testing.T) {
-	buf := make([]byte, 10)
-	_, errno := unix.Write(tfd[1], buf)
-	if errno != nil {
-		t.Fatalf("Failed to write to pipe: %v", errno)
-	}
-}
-
-func (tfd testFd) get(t *testing.T) {
-	buf := make([]byte, 10)
-	_, errno := unix.Read(tfd[0], buf)
-	if errno != nil {
-		t.Fatalf("Failed to read from pipe: %v", errno)
-	}
-}
-
-func (tfd testFd) close() {
-	unix.Close(tfd[1])
-	unix.Close(tfd[0])
-}
-
-func makePoller(t *testing.T) (testFd, *fdPoller) {
-	tfd := makeTestFd(t)
-	poller, err := newFdPoller(tfd.fd())
-	if err != nil {
-		t.Fatalf("Failed to create poller: %v", err)
-	}
-	return tfd, poller
-}
-
-func TestPollerWithBadFd(t *testing.T) {
-	_, err := newFdPoller(-1)
-	if err != unix.EBADF {
-		t.Fatalf("Expected EBADF, got: %v", err)
-	}
-}
-
-func TestPollerWithData(t *testing.T) {
-	tfd, poller := makePoller(t)
-	defer tfd.close()
-	defer poller.close()
-
-	tfd.put(t)
-	ok, err := poller.wait()
-	if err != nil {
-		t.Fatalf("poller failed: %v", err)
-	}
-	if !ok {
-		t.Fatalf("expected poller to return true")
-	}
-	tfd.get(t)
-}
-
-func TestPollerWithWakeup(t *testing.T) {
-	tfd, poller := makePoller(t)
-	defer tfd.close()
-	defer poller.close()
-
-	err := poller.wake()
-	if err != nil {
-		t.Fatalf("wake failed: %v", err)
-	}
-	ok, err := poller.wait()
-	if err != nil {
-		t.Fatalf("poller failed: %v", err)
-	}
-	if ok {
-		t.Fatalf("expected poller to return false")
-	}
-}
-
-func TestPollerWithClose(t *testing.T) {
-	tfd, poller := makePoller(t)
-	defer tfd.close()
-	defer poller.close()
-
-	tfd.closeWrite(t)
-	ok, err := poller.wait()
-	if err != nil {
-		t.Fatalf("poller failed: %v", err)
-	}
-	if !ok {
-		t.Fatalf("expected poller to return true")
-	}
-}
-
-func TestPollerWithWakeupAndData(t *testing.T) {
-	tfd, poller := makePoller(t)
-	defer tfd.close()
-	defer poller.close()
-
-	tfd.put(t)
-	err := poller.wake()
-	if err != nil {
-		t.Fatalf("wake failed: %v", err)
-	}
-
-	// both data and wakeup
-	ok, err := poller.wait()
-	if err != nil {
-		t.Fatalf("poller failed: %v", err)
-	}
-	if !ok {
-		t.Fatalf("expected poller to return true")
-	}
-
-	// data is still in the buffer, wakeup is cleared
-	ok, err = poller.wait()
-	if err != nil {
-		t.Fatalf("poller failed: %v", err)
-	}
-	if !ok {
-		t.Fatalf("expected poller to return true")
-	}
-
-	tfd.get(t)
-	// data is gone, only wakeup now
-	err = poller.wake()
-	if err != nil {
-		t.Fatalf("wake failed: %v", err)
-	}
-	ok, err = poller.wait()
-	if err != nil {
-		t.Fatalf("poller failed: %v", err)
-	}
-	if ok {
-		t.Fatalf("expected poller to return false")
-	}
-}
-
-func TestPollerConcurrent(t *testing.T) {
-	tfd, poller := makePoller(t)
-	defer tfd.close()
-	defer poller.close()
-
-	oks := make(chan bool)
-	live := make(chan bool)
-	defer close(live)
-	go func() {
-		defer close(oks)
-		for {
-			ok, err := poller.wait()
-			if err != nil {
-				t.Fatalf("poller failed: %v", err)
-			}
-			oks <- ok
-			if !<-live {
-				return
-			}
-		}
-	}()
-
-	// Try a write
-	select {
-	case <-time.After(50 * time.Millisecond):
-	case <-oks:
-		t.Fatalf("poller did not wait")
-	}
-	tfd.put(t)
-	if !<-oks {
-		t.Fatalf("expected true")
-	}
-	tfd.get(t)
-	live <- true
-
-	// Try a wakeup
-	select {
-	case <-time.After(50 * time.Millisecond):
-	case <-oks:
-		t.Fatalf("poller did not wait")
-	}
-	err := poller.wake()
-	if err != nil {
-		t.Fatalf("wake failed: %v", err)
-	}
-	if <-oks {
-		t.Fatalf("expected false")
-	}
-	live <- true
-
-	// Try a close
-	select {
-	case <-time.After(50 * time.Millisecond):
-	case <-oks:
-		t.Fatalf("poller did not wait")
-	}
-	tfd.closeWrite(t)
-	if !<-oks {
-		t.Fatalf("expected true")
-	}
-	tfd.get(t)
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify_test.go b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify_test.go
deleted file mode 100644
index 2527cad..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/inotify_test.go
+++ /dev/null
@@ -1,344 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build linux
-
-package fsnotify
-
-import (
-	"fmt"
-	"os"
-	"path/filepath"
-	"testing"
-	"time"
-
-	"golang.org/x/sys/unix"
-)
-
-func TestInotifyCloseRightAway(t *testing.T) {
-	w, err := NewWatcher()
-	if err != nil {
-		t.Fatalf("Failed to create watcher")
-	}
-
-	// Close immediately; it won't even reach the first unix.Read.
-	w.Close()
-
-	// Wait for the close to complete.
-	<-time.After(50 * time.Millisecond)
-	isWatcherReallyClosed(t, w)
-}
-
-func TestInotifyCloseSlightlyLater(t *testing.T) {
-	w, err := NewWatcher()
-	if err != nil {
-		t.Fatalf("Failed to create watcher")
-	}
-
-	// Wait until readEvents has reached unix.Read, and Close.
-	<-time.After(50 * time.Millisecond)
-	w.Close()
-
-	// Wait for the close to complete.
-	<-time.After(50 * time.Millisecond)
-	isWatcherReallyClosed(t, w)
-}
-
-func TestInotifyCloseSlightlyLaterWithWatch(t *testing.T) {
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	w, err := NewWatcher()
-	if err != nil {
-		t.Fatalf("Failed to create watcher")
-	}
-	w.Add(testDir)
-
-	// Wait until readEvents has reached unix.Read, and Close.
-	<-time.After(50 * time.Millisecond)
-	w.Close()
-
-	// Wait for the close to complete.
-	<-time.After(50 * time.Millisecond)
-	isWatcherReallyClosed(t, w)
-}
-
-func TestInotifyCloseAfterRead(t *testing.T) {
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	w, err := NewWatcher()
-	if err != nil {
-		t.Fatalf("Failed to create watcher")
-	}
-
-	err = w.Add(testDir)
-	if err != nil {
-		t.Fatalf("Failed to add .")
-	}
-
-	// Generate an event.
-	os.Create(filepath.Join(testDir, "somethingSOMETHINGsomethingSOMETHING"))
-
-	// Wait for readEvents to read the event, then close the watcher.
-	<-time.After(50 * time.Millisecond)
-	w.Close()
-
-	// Wait for the close to complete.
-	<-time.After(50 * time.Millisecond)
-	isWatcherReallyClosed(t, w)
-}
-
-func isWatcherReallyClosed(t *testing.T, w *Watcher) {
-	select {
-	case err, ok := <-w.Errors:
-		if ok {
-			t.Fatalf("w.Errors is not closed; readEvents is still alive after closing (error: %v)", err)
-		}
-	default:
-		t.Fatalf("w.Errors would have blocked; readEvents is still alive!")
-	}
-
-	select {
-	case _, ok := <-w.Events:
-		if ok {
-			t.Fatalf("w.Events is not closed; readEvents is still alive after closing")
-		}
-	default:
-		t.Fatalf("w.Events would have blocked; readEvents is still alive!")
-	}
-}
-
-func TestInotifyCloseCreate(t *testing.T) {
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	w, err := NewWatcher()
-	if err != nil {
-		t.Fatalf("Failed to create watcher: %v", err)
-	}
-	defer w.Close()
-
-	err = w.Add(testDir)
-	if err != nil {
-		t.Fatalf("Failed to add testDir: %v", err)
-	}
-	h, err := os.Create(filepath.Join(testDir, "testfile"))
-	if err != nil {
-		t.Fatalf("Failed to create file in testdir: %v", err)
-	}
-	h.Close()
-	select {
-	case _ = <-w.Events:
-	case err := <-w.Errors:
-		t.Fatalf("Error from watcher: %v", err)
-	case <-time.After(50 * time.Millisecond):
-		t.Fatalf("Took too long to wait for event")
-	}
-
-	// At this point, we've received one event, so the goroutine is ready.
-	// It's also blocking on unix.Read.
-	// Now we try to swap the file descriptor under its nose.
-	w.Close()
-	w, err = NewWatcher()
-	defer w.Close()
-	if err != nil {
-		t.Fatalf("Failed to create second watcher: %v", err)
-	}
-
-	<-time.After(50 * time.Millisecond)
-	err = w.Add(testDir)
-	if err != nil {
-		t.Fatalf("Error adding testDir again: %v", err)
-	}
-}
-
-func TestInotifyStress(t *testing.T) {
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-	testFile := filepath.Join(testDir, "testfile")
-
-	w, err := NewWatcher()
-	if err != nil {
-		t.Fatalf("Failed to create watcher: %v", err)
-	}
-	defer w.Close()
-
-	killchan := make(chan struct{})
-	defer close(killchan)
-
-	err = w.Add(testDir)
-	if err != nil {
-		t.Fatalf("Failed to add testDir: %v", err)
-	}
-
-	proc, err := os.FindProcess(os.Getpid())
-	if err != nil {
-		t.Fatalf("Error finding process: %v", err)
-	}
-
-	go func() {
-		for {
-			select {
-			case <-time.After(5 * time.Millisecond):
-				err := proc.Signal(unix.SIGUSR1)
-				if err != nil {
-					t.Fatalf("Signal failed: %v", err)
-				}
-			case <-killchan:
-				return
-			}
-		}
-	}()
-
-	go func() {
-		for {
-			select {
-			case <-time.After(11 * time.Millisecond):
-				err := w.poller.wake()
-				if err != nil {
-					t.Fatalf("Wake failed: %v", err)
-				}
-			case <-killchan:
-				return
-			}
-		}
-	}()
-
-	go func() {
-		for {
-			select {
-			case <-killchan:
-				return
-			default:
-				handle, err := os.Create(testFile)
-				if err != nil {
-					t.Fatalf("Create failed: %v", err)
-				}
-				handle.Close()
-				time.Sleep(time.Millisecond)
-				err = os.Remove(testFile)
-				if err != nil {
-					t.Fatalf("Remove failed: %v", err)
-				}
-			}
-		}
-	}()
-
-	creates := 0
-	removes := 0
-	after := time.After(5 * time.Second)
-	for {
-		select {
-		case <-after:
-			if creates-removes > 1 || creates-removes < -1 {
-				t.Fatalf("Creates and removes should not be off by more than one: %d creates, %d removes", creates, removes)
-			}
-			if creates < 50 {
-				t.Fatalf("Expected at least 50 creates, got %d", creates)
-			}
-			return
-		case err := <-w.Errors:
-			t.Fatalf("Got an error from watcher: %v", err)
-		case evt := <-w.Events:
-			if evt.Name != testFile {
-				t.Fatalf("Got an event for an unknown file: %s", evt.Name)
-			}
-			if evt.Op == Create {
-				creates++
-			}
-			if evt.Op == Remove {
-				removes++
-			}
-		}
-	}
-}
-
-func TestInotifyRemoveTwice(t *testing.T) {
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-	testFile := filepath.Join(testDir, "testfile")
-
-	handle, err := os.Create(testFile)
-	if err != nil {
-		t.Fatalf("Create failed: %v", err)
-	}
-	handle.Close()
-
-	w, err := NewWatcher()
-	if err != nil {
-		t.Fatalf("Failed to create watcher: %v", err)
-	}
-	defer w.Close()
-
-	err = w.Add(testFile)
-	if err != nil {
-		t.Fatalf("Failed to add testFile: %v", err)
-	}
-
-	err = os.Remove(testFile)
-	if err != nil {
-		t.Fatalf("Failed to remove testFile: %v", err)
-	}
-
-	err = w.Remove(testFile)
-	if err == nil {
-		t.Fatalf("no error on removing invalid file")
-	}
-	s1 := fmt.Sprintf("%s", err)
-
-	err = w.Remove(testFile)
-	if err == nil {
-		t.Fatalf("no error on removing invalid file")
-	}
-	s2 := fmt.Sprintf("%s", err)
-
-	if s1 != s2 {
-		t.Fatalf("receive different error - %s / %s", s1, s2)
-	}
-}
-
-func TestInotifyInnerMapLength(t *testing.T) {
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-	testFile := filepath.Join(testDir, "testfile")
-
-	handle, err := os.Create(testFile)
-	if err != nil {
-		t.Fatalf("Create failed: %v", err)
-	}
-	handle.Close()
-
-	w, err := NewWatcher()
-	if err != nil {
-		t.Fatalf("Failed to create watcher: %v", err)
-	}
-	defer w.Close()
-
-	err = w.Add(testFile)
-	if err != nil {
-		t.Fatalf("Failed to add testFile: %v", err)
-	}
-	go func() {
-		for err := range w.Errors {
-			t.Fatalf("error received: %s", err)
-		}
-	}()
-
-	err = os.Remove(testFile)
-	if err != nil {
-		t.Fatalf("Failed to remove testFile: %v", err)
-	}
-	_ = <-w.Events                      // consume Remove event
-	<-time.After(50 * time.Millisecond) // wait IN_IGNORE propagated
-
-	w.mu.Lock()
-	defer w.mu.Unlock()
-	if len(w.watches) != 0 {
-		t.Fatalf("Expected watches len is 0, but got: %d, %v", len(w.watches), w.watches)
-	}
-	if len(w.paths) != 0 {
-		t.Fatalf("Expected paths len is 0, but got: %d, %v", len(w.paths), w.paths)
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/integration_darwin_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/integration_darwin_test.go b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/integration_darwin_test.go
deleted file mode 100644
index 5564554..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/integration_darwin_test.go
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2016 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package fsnotify
-
-import (
-	"os"
-	"path/filepath"
-	"testing"
-	"time"
-
-	"golang.org/x/sys/unix"
-)
-
-// testExchangedataForWatcher tests the watcher with the exchangedata operation on OS X.
-//
-// This is widely used for atomic saves on OS X, e.g. TextMate and in Apple's NSDocument.
-//
-// See https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html
-// Also see: https://github.com/textmate/textmate/blob/cd016be29489eba5f3c09b7b70b06da134dda550/Frameworks/io/src/swap_file_data.cc#L20
-func testExchangedataForWatcher(t *testing.T, watchDir bool) {
-	// Create directory to watch
-	testDir1 := tempMkdir(t)
-
-	// For the intermediate file
-	testDir2 := tempMkdir(t)
-
-	defer os.RemoveAll(testDir1)
-	defer os.RemoveAll(testDir2)
-
-	resolvedFilename := "TestFsnotifyEvents.file"
-
-	// TextMate does:
-	//
-	// 1. exchangedata (intermediate, resolved)
-	// 2. unlink intermediate
-	//
-	// Let's try to simulate that:
-	resolved := filepath.Join(testDir1, resolvedFilename)
-	intermediate := filepath.Join(testDir2, resolvedFilename+"~")
-
-	// Make sure we create the file before we start watching
-	createAndSyncFile(t, resolved)
-
-	watcher := newWatcher(t)
-
-	// Test both variants in isolation
-	if watchDir {
-		addWatch(t, watcher, testDir1)
-	} else {
-		addWatch(t, watcher, resolved)
-	}
-
-	// Receive errors on the error channel on a separate goroutine
-	go func() {
-		for err := range watcher.Errors {
-			t.Fatalf("error received: %s", err)
-		}
-	}()
-
-	// Receive events on the event channel on a separate goroutine
-	eventstream := watcher.Events
-	var removeReceived counter
-	var createReceived counter
-
-	done := make(chan bool)
-
-	go func() {
-		for event := range eventstream {
-			// Only count relevant events
-			if event.Name == filepath.Clean(resolved) {
-				if event.Op&Remove == Remove {
-					removeReceived.increment()
-				}
-				if event.Op&Create == Create {
-					createReceived.increment()
-				}
-			}
-			t.Logf("event received: %s", event)
-		}
-		done <- true
-	}()
-
-	// Repeat to make sure the watched file/directory "survives" the REMOVE/CREATE loop.
-	for i := 1; i <= 3; i++ {
-		// The intermediate file is created in a folder outside the watcher
-		createAndSyncFile(t, intermediate)
-
-		// 1. Swap
-		if err := unix.Exchangedata(intermediate, resolved, 0); err != nil {
-			t.Fatalf("[%d] exchangedata failed: %s", i, err)
-		}
-
-		time.Sleep(50 * time.Millisecond)
-
-		// 2. Delete the intermediate file
-		err := os.Remove(intermediate)
-
-		if err != nil {
-			t.Fatalf("[%d] remove %s failed: %s", i, intermediate, err)
-		}
-
-		time.Sleep(50 * time.Millisecond)
-
-	}
-
-	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
-	time.Sleep(500 * time.Millisecond)
-
-	// The events will be (CHMOD + REMOVE + CREATE) X 2. Let's focus on the last two:
-	if removeReceived.value() < 3 {
-		t.Fatal("fsnotify remove events have not been received after 500 ms")
-	}
-
-	if createReceived.value() < 3 {
-		t.Fatal("fsnotify create events have not been received after 500 ms")
-	}
-
-	watcher.Close()
-	t.Log("waiting for the event channel to become closed...")
-	select {
-	case <-done:
-		t.Log("event channel closed")
-	case <-time.After(2 * time.Second):
-		t.Fatal("event stream was not closed after 2 seconds")
-	}
-}
-
-// TestExchangedataInWatchedDir test exchangedata operation on file in watched dir.
-func TestExchangedataInWatchedDir(t *testing.T) {
-	testExchangedataForWatcher(t, true)
-}
-
-// TestExchangedataInWatchedDir test exchangedata operation on watched file.
-func TestExchangedataInWatchedFile(t *testing.T) {
-	testExchangedataForWatcher(t, false)
-}
-
-func createAndSyncFile(t *testing.T, filepath string) {
-	f1, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0666)
-	if err != nil {
-		t.Fatalf("creating %s failed: %s", filepath, err)
-	}
-	f1.Sync()
-	f1.Close()
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/integration_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/integration_test.go b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/integration_test.go
deleted file mode 100644
index 8b7e9d3..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/integration_test.go
+++ /dev/null
@@ -1,1237 +0,0 @@
-// Copyright 2010 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build !plan9,!solaris
-
-package fsnotify
-
-import (
-	"io/ioutil"
-	"os"
-	"os/exec"
-	"path"
-	"path/filepath"
-	"runtime"
-	"sync/atomic"
-	"testing"
-	"time"
-)
-
-// An atomic counter
-type counter struct {
-	val int32
-}
-
-func (c *counter) increment() {
-	atomic.AddInt32(&c.val, 1)
-}
-
-func (c *counter) value() int32 {
-	return atomic.LoadInt32(&c.val)
-}
-
-func (c *counter) reset() {
-	atomic.StoreInt32(&c.val, 0)
-}
-
-// tempMkdir makes a temporary directory
-func tempMkdir(t *testing.T) string {
-	dir, err := ioutil.TempDir("", "fsnotify")
-	if err != nil {
-		t.Fatalf("failed to create test directory: %s", err)
-	}
-	return dir
-}
-
-// tempMkFile makes a temporary file.
-func tempMkFile(t *testing.T, dir string) string {
-	f, err := ioutil.TempFile(dir, "fsnotify")
-	if err != nil {
-		t.Fatalf("failed to create test file: %v", err)
-	}
-	defer f.Close()
-	return f.Name()
-}
-
-// newWatcher initializes an fsnotify Watcher instance.
-func newWatcher(t *testing.T) *Watcher {
-	watcher, err := NewWatcher()
-	if err != nil {
-		t.Fatalf("NewWatcher() failed: %s", err)
-	}
-	return watcher
-}
-
-// addWatch adds a watch for a directory
-func addWatch(t *testing.T, watcher *Watcher, dir string) {
-	if err := watcher.Add(dir); err != nil {
-		t.Fatalf("watcher.Add(%q) failed: %s", dir, err)
-	}
-}
-
-func TestFsnotifyMultipleOperations(t *testing.T) {
-	watcher := newWatcher(t)
-
-	// Receive errors on the error channel on a separate goroutine
-	go func() {
-		for err := range watcher.Errors {
-			t.Fatalf("error received: %s", err)
-		}
-	}()
-
-	// Create directory to watch
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	// Create directory that's not watched
-	testDirToMoveFiles := tempMkdir(t)
-	defer os.RemoveAll(testDirToMoveFiles)
-
-	testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile")
-	testFileRenamed := filepath.Join(testDirToMoveFiles, "TestFsnotifySeqRename.testfile")
-
-	addWatch(t, watcher, testDir)
-
-	// Receive events on the event channel on a separate goroutine
-	eventstream := watcher.Events
-	var createReceived, modifyReceived, deleteReceived, renameReceived counter
-	done := make(chan bool)
-	go func() {
-		for event := range eventstream {
-			// Only count relevant events
-			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
-				t.Logf("event received: %s", event)
-				if event.Op&Remove == Remove {
-					deleteReceived.increment()
-				}
-				if event.Op&Write == Write {
-					modifyReceived.increment()
-				}
-				if event.Op&Create == Create {
-					createReceived.increment()
-				}
-				if event.Op&Rename == Rename {
-					renameReceived.increment()
-				}
-			} else {
-				t.Logf("unexpected event received: %s", event)
-			}
-		}
-		done <- true
-	}()
-
-	// Create a file
-	// This should add at least one event to the fsnotify event queue
-	var f *os.File
-	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
-	if err != nil {
-		t.Fatalf("creating test file failed: %s", err)
-	}
-	f.Sync()
-
-	time.Sleep(time.Millisecond)
-	f.WriteString("data")
-	f.Sync()
-	f.Close()
-
-	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
-
-	if err := testRename(testFile, testFileRenamed); err != nil {
-		t.Fatalf("rename failed: %s", err)
-	}
-
-	// Modify the file outside of the watched dir
-	f, err = os.Open(testFileRenamed)
-	if err != nil {
-		t.Fatalf("open test renamed file failed: %s", err)
-	}
-	f.WriteString("data")
-	f.Sync()
-	f.Close()
-
-	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
-
-	// Recreate the file that was moved
-	f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
-	if err != nil {
-		t.Fatalf("creating test file failed: %s", err)
-	}
-	f.Close()
-	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
-
-	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
-	time.Sleep(500 * time.Millisecond)
-	cReceived := createReceived.value()
-	if cReceived != 2 {
-		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
-	}
-	mReceived := modifyReceived.value()
-	if mReceived != 1 {
-		t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1)
-	}
-	dReceived := deleteReceived.value()
-	rReceived := renameReceived.value()
-	if dReceived+rReceived != 1 {
-		t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", rReceived+dReceived, 1)
-	}
-
-	// Try closing the fsnotify instance
-	t.Log("calling Close()")
-	watcher.Close()
-	t.Log("waiting for the event channel to become closed...")
-	select {
-	case <-done:
-		t.Log("event channel closed")
-	case <-time.After(2 * time.Second):
-		t.Fatal("event stream was not closed after 2 seconds")
-	}
-}
-
-func TestFsnotifyMultipleCreates(t *testing.T) {
-	watcher := newWatcher(t)
-
-	// Receive errors on the error channel on a separate goroutine
-	go func() {
-		for err := range watcher.Errors {
-			t.Fatalf("error received: %s", err)
-		}
-	}()
-
-	// Create directory to watch
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile")
-
-	addWatch(t, watcher, testDir)
-
-	// Receive events on the event channel on a separate goroutine
-	eventstream := watcher.Events
-	var createReceived, modifyReceived, deleteReceived counter
-	done := make(chan bool)
-	go func() {
-		for event := range eventstream {
-			// Only count relevant events
-			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
-				t.Logf("event received: %s", event)
-				if event.Op&Remove == Remove {
-					deleteReceived.increment()
-				}
-				if event.Op&Create == Create {
-					createReceived.increment()
-				}
-				if event.Op&Write == Write {
-					modifyReceived.increment()
-				}
-			} else {
-				t.Logf("unexpected event received: %s", event)
-			}
-		}
-		done <- true
-	}()
-
-	// Create a file
-	// This should add at least one event to the fsnotify event queue
-	var f *os.File
-	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
-	if err != nil {
-		t.Fatalf("creating test file failed: %s", err)
-	}
-	f.Sync()
-
-	time.Sleep(time.Millisecond)
-	f.WriteString("data")
-	f.Sync()
-	f.Close()
-
-	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
-
-	os.Remove(testFile)
-
-	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
-
-	// Recreate the file
-	f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
-	if err != nil {
-		t.Fatalf("creating test file failed: %s", err)
-	}
-	f.Close()
-	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
-
-	// Modify
-	f, err = os.OpenFile(testFile, os.O_WRONLY, 0666)
-	if err != nil {
-		t.Fatalf("creating test file failed: %s", err)
-	}
-	f.Sync()
-
-	time.Sleep(time.Millisecond)
-	f.WriteString("data")
-	f.Sync()
-	f.Close()
-
-	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
-
-	// Modify
-	f, err = os.OpenFile(testFile, os.O_WRONLY, 0666)
-	if err != nil {
-		t.Fatalf("creating test file failed: %s", err)
-	}
-	f.Sync()
-
-	time.Sleep(time.Millisecond)
-	f.WriteString("data")
-	f.Sync()
-	f.Close()
-
-	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
-
-	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
-	time.Sleep(500 * time.Millisecond)
-	cReceived := createReceived.value()
-	if cReceived != 2 {
-		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
-	}
-	mReceived := modifyReceived.value()
-	if mReceived < 3 {
-		t.Fatalf("incorrect number of modify events received after 500 ms (%d vs atleast %d)", mReceived, 3)
-	}
-	dReceived := deleteReceived.value()
-	if dReceived != 1 {
-		t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", dReceived, 1)
-	}
-
-	// Try closing the fsnotify instance
-	t.Log("calling Close()")
-	watcher.Close()
-	t.Log("waiting for the event channel to become closed...")
-	select {
-	case <-done:
-		t.Log("event channel closed")
-	case <-time.After(2 * time.Second):
-		t.Fatal("event stream was not closed after 2 seconds")
-	}
-}
-
-func TestFsnotifyDirOnly(t *testing.T) {
-	watcher := newWatcher(t)
-
-	// Create directory to watch
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	// Create a file before watching directory
-	// This should NOT add any events to the fsnotify event queue
-	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
-	{
-		var f *os.File
-		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
-		if err != nil {
-			t.Fatalf("creating test file failed: %s", err)
-		}
-		f.Sync()
-		f.Close()
-	}
-
-	addWatch(t, watcher, testDir)
-
-	// Receive errors on the error channel on a separate goroutine
-	go func() {
-		for err := range watcher.Errors {
-			t.Fatalf("error received: %s", err)
-		}
-	}()
-
-	testFile := filepath.Join(testDir, "TestFsnotifyDirOnly.testfile")
-
-	// Receive events on the event channel on a separate goroutine
-	eventstream := watcher.Events
-	var createReceived, modifyReceived, deleteReceived counter
-	done := make(chan bool)
-	go func() {
-		for event := range eventstream {
-			// Only count relevant events
-			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileAlreadyExists) {
-				t.Logf("event received: %s", event)
-				if event.Op&Remove == Remove {
-					deleteReceived.increment()
-				}
-				if event.Op&Write == Write {
-					modifyReceived.increment()
-				}
-				if event.Op&Create == Create {
-					createReceived.increment()
-				}
-			} else {
-				t.Logf("unexpected event received: %s", event)
-			}
-		}
-		done <- true
-	}()
-
-	// Create a file
-	// This should add at least one event to the fsnotify event queue
-	var f *os.File
-	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
-	if err != nil {
-		t.Fatalf("creating test file failed: %s", err)
-	}
-	f.Sync()
-
-	time.Sleep(time.Millisecond)
-	f.WriteString("data")
-	f.Sync()
-	f.Close()
-
-	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
-
-	os.Remove(testFile)
-	os.Remove(testFileAlreadyExists)
-
-	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
-	time.Sleep(500 * time.Millisecond)
-	cReceived := createReceived.value()
-	if cReceived != 1 {
-		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 1)
-	}
-	mReceived := modifyReceived.value()
-	if mReceived != 1 {
-		t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1)
-	}
-	dReceived := deleteReceived.value()
-	if dReceived != 2 {
-		t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2)
-	}
-
-	// Try closing the fsnotify instance
-	t.Log("calling Close()")
-	watcher.Close()
-	t.Log("waiting for the event channel to become closed...")
-	select {
-	case <-done:
-		t.Log("event channel closed")
-	case <-time.After(2 * time.Second):
-		t.Fatal("event stream was not closed after 2 seconds")
-	}
-}
-
-func TestFsnotifyDeleteWatchedDir(t *testing.T) {
-	watcher := newWatcher(t)
-	defer watcher.Close()
-
-	// Create directory to watch
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	// Create a file before watching directory
-	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
-	{
-		var f *os.File
-		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
-		if err != nil {
-			t.Fatalf("creating test file failed: %s", err)
-		}
-		f.Sync()
-		f.Close()
-	}
-
-	addWatch(t, watcher, testDir)
-
-	// Add a watch for testFile
-	addWatch(t, watcher, testFileAlreadyExists)
-
-	// Receive errors on the error channel on a separate goroutine
-	go func() {
-		for err := range watcher.Errors {
-			t.Fatalf("error received: %s", err)
-		}
-	}()
-
-	// Receive events on the event channel on a separate goroutine
-	eventstream := watcher.Events
-	var deleteReceived counter
-	go func() {
-		for event := range eventstream {
-			// Only count relevant events
-			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFileAlreadyExists) {
-				t.Logf("event received: %s", event)
-				if event.Op&Remove == Remove {
-					deleteReceived.increment()
-				}
-			} else {
-				t.Logf("unexpected event received: %s", event)
-			}
-		}
-	}()
-
-	os.RemoveAll(testDir)
-
-	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
-	time.Sleep(500 * time.Millisecond)
-	dReceived := deleteReceived.value()
-	if dReceived < 2 {
-		t.Fatalf("did not receive at least %d delete events, received %d after 500 ms", 2, dReceived)
-	}
-}
-
-func TestFsnotifySubDir(t *testing.T) {
-	watcher := newWatcher(t)
-
-	// Create directory to watch
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	testFile1 := filepath.Join(testDir, "TestFsnotifyFile1.testfile")
-	testSubDir := filepath.Join(testDir, "sub")
-	testSubDirFile := filepath.Join(testDir, "sub/TestFsnotifyFile1.testfile")
-
-	// Receive errors on the error channel on a separate goroutine
-	go func() {
-		for err := range watcher.Errors {
-			t.Fatalf("error received: %s", err)
-		}
-	}()
-
-	// Receive events on the event channel on a separate goroutine
-	eventstream := watcher.Events
-	var createReceived, deleteReceived counter
-	done := make(chan bool)
-	go func() {
-		for event := range eventstream {
-			// Only count relevant events
-			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testSubDir) || event.Name == filepath.Clean(testFile1) {
-				t.Logf("event received: %s", event)
-				if event.Op&Create == Create {
-					createReceived.increment()
-				}
-				if event.Op&Remove == Remove {
-					deleteReceived.increment()
-				}
-			} else {
-				t.Logf("unexpected event received: %s", event)
-			}
-		}
-		done <- true
-	}()
-
-	addWatch(t, watcher, testDir)
-
-	// Create sub-directory
-	if err := os.Mkdir(testSubDir, 0777); err != nil {
-		t.Fatalf("failed to create test sub-directory: %s", err)
-	}
-
-	// Create a file
-	var f *os.File
-	f, err := os.OpenFile(testFile1, os.O_WRONLY|os.O_CREATE, 0666)
-	if err != nil {
-		t.Fatalf("creating test file failed: %s", err)
-	}
-	f.Sync()
-	f.Close()
-
-	// Create a file (Should not see this! we are not watching subdir)
-	var fs *os.File
-	fs, err = os.OpenFile(testSubDirFile, os.O_WRONLY|os.O_CREATE, 0666)
-	if err != nil {
-		t.Fatalf("creating test file failed: %s", err)
-	}
-	fs.Sync()
-	fs.Close()
-
-	time.Sleep(200 * time.Millisecond)
-
-	// Make sure receive deletes for both file and sub-directory
-	os.RemoveAll(testSubDir)
-	os.Remove(testFile1)
-
-	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
-	time.Sleep(500 * time.Millisecond)
-	cReceived := createReceived.value()
-	if cReceived != 2 {
-		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
-	}
-	dReceived := deleteReceived.value()
-	if dReceived != 2 {
-		t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2)
-	}
-
-	// Try closing the fsnotify instance
-	t.Log("calling Close()")
-	watcher.Close()
-	t.Log("waiting for the event channel to become closed...")
-	select {
-	case <-done:
-		t.Log("event channel closed")
-	case <-time.After(2 * time.Second):
-		t.Fatal("event stream was not closed after 2 seconds")
-	}
-}
-
-func TestFsnotifyRename(t *testing.T) {
-	watcher := newWatcher(t)
-
-	// Create directory to watch
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	addWatch(t, watcher, testDir)
-
-	// Receive errors on the error channel on a separate goroutine
-	go func() {
-		for err := range watcher.Errors {
-			t.Fatalf("error received: %s", err)
-		}
-	}()
-
-	testFile := filepath.Join(testDir, "TestFsnotifyEvents.testfile")
-	testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
-
-	// Receive events on the event channel on a separate goroutine
-	eventstream := watcher.Events
-	var renameReceived counter
-	done := make(chan bool)
-	go func() {
-		for event := range eventstream {
-			// Only count relevant events
-			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) {
-				if event.Op&Rename == Rename {
-					renameReceived.increment()
-				}
-				t.Logf("event received: %s", event)
-			} else {
-				t.Logf("unexpected event received: %s", event)
-			}
-		}
-		done <- true
-	}()
-
-	// Create a file
-	// This should add at least one event to the fsnotify event queue
-	var f *os.File
-	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
-	if err != nil {
-		t.Fatalf("creating test file failed: %s", err)
-	}
-	f.Sync()
-
-	f.WriteString("data")
-	f.Sync()
-	f.Close()
-
-	// Add a watch for testFile
-	addWatch(t, watcher, testFile)
-
-	if err := testRename(testFile, testFileRenamed); err != nil {
-		t.Fatalf("rename failed: %s", err)
-	}
-
-	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
-	time.Sleep(500 * time.Millisecond)
-	if renameReceived.value() == 0 {
-		t.Fatal("fsnotify rename events have not been received after 500 ms")
-	}
-
-	// Try closing the fsnotify instance
-	t.Log("calling Close()")
-	watcher.Close()
-	t.Log("waiting for the event channel to become closed...")
-	select {
-	case <-done:
-		t.Log("event channel closed")
-	case <-time.After(2 * time.Second):
-		t.Fatal("event stream was not closed after 2 seconds")
-	}
-
-	os.Remove(testFileRenamed)
-}
-
-func TestFsnotifyRenameToCreate(t *testing.T) {
-	watcher := newWatcher(t)
-
-	// Create directory to watch
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	// Create directory to get file
-	testDirFrom := tempMkdir(t)
-	defer os.RemoveAll(testDirFrom)
-
-	addWatch(t, watcher, testDir)
-
-	// Receive errors on the error channel on a separate goroutine
-	go func() {
-		for err := range watcher.Errors {
-			t.Fatalf("error received: %s", err)
-		}
-	}()
-
-	testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile")
-	testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
-
-	// Receive events on the event channel on a separate goroutine
-	eventstream := watcher.Events
-	var createReceived counter
-	done := make(chan bool)
-	go func() {
-		for event := range eventstream {
-			// Only count relevant events
-			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) {
-				if event.Op&Create == Create {
-					createReceived.increment()
-				}
-				t.Logf("event received: %s", event)
-			} else {
-				t.Logf("unexpected event received: %s", event)
-			}
-		}
-		done <- true
-	}()
-
-	// Create a file
-	// This should add at least one event to the fsnotify event queue
-	var f *os.File
-	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
-	if err != nil {
-		t.Fatalf("creating test file failed: %s", err)
-	}
-	f.Sync()
-	f.Close()
-
-	if err := testRename(testFile, testFileRenamed); err != nil {
-		t.Fatalf("rename failed: %s", err)
-	}
-
-	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
-	time.Sleep(500 * time.Millisecond)
-	if createReceived.value() == 0 {
-		t.Fatal("fsnotify create events have not been received after 500 ms")
-	}
-
-	// Try closing the fsnotify instance
-	t.Log("calling Close()")
-	watcher.Close()
-	t.Log("waiting for the event channel to become closed...")
-	select {
-	case <-done:
-		t.Log("event channel closed")
-	case <-time.After(2 * time.Second):
-		t.Fatal("event stream was not closed after 2 seconds")
-	}
-
-	os.Remove(testFileRenamed)
-}
-
-func TestFsnotifyRenameToOverwrite(t *testing.T) {
-	switch runtime.GOOS {
-	case "plan9", "windows":
-		t.Skipf("skipping test on %q (os.Rename over existing file does not create event).", runtime.GOOS)
-	}
-
-	watcher := newWatcher(t)
-
-	// Create directory to watch
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	// Create directory to get file
-	testDirFrom := tempMkdir(t)
-	defer os.RemoveAll(testDirFrom)
-
-	testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile")
-	testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
-
-	// Create a file
-	var fr *os.File
-	fr, err := os.OpenFile(testFileRenamed, os.O_WRONLY|os.O_CREATE, 0666)
-	if err != nil {
-		t.Fatalf("creating test file failed: %s", err)
-	}
-	fr.Sync()
-	fr.Close()
-
-	addWatch(t, watcher, testDir)
-
-	// Receive errors on the error channel on a separate goroutine
-	go func() {
-		for err := range watcher.Errors {
-			t.Fatalf("error received: %s", err)
-		}
-	}()
-
-	// Receive events on the event channel on a separate goroutine
-	eventstream := watcher.Events
-	var eventReceived counter
-	done := make(chan bool)
-	go func() {
-		for event := range eventstream {
-			// Only count relevant events
-			if event.Name == filepath.Clean(testFileRenamed) {
-				eventReceived.increment()
-				t.Logf("event received: %s", event)
-			} else {
-				t.Logf("unexpected event received: %s", event)
-			}
-		}
-		done <- true
-	}()
-
-	// Create a file
-	// This should add at least one event to the fsnotify event queue
-	var f *os.File
-	f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
-	if err != nil {
-		t.Fatalf("creating test file failed: %s", err)
-	}
-	f.Sync()
-	f.Close()
-
-	if err := testRename(testFile, testFileRenamed); err != nil {
-		t.Fatalf("rename failed: %s", err)
-	}
-
-	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
-	time.Sleep(500 * time.Millisecond)
-	if eventReceived.value() == 0 {
-		t.Fatal("fsnotify events have not been received after 500 ms")
-	}
-
-	// Try closing the fsnotify instance
-	t.Log("calling Close()")
-	watcher.Close()
-	t.Log("waiting for the event channel to become closed...")
-	select {
-	case <-done:
-		t.Log("event channel closed")
-	case <-time.After(2 * time.Second):
-		t.Fatal("event stream was not closed after 2 seconds")
-	}
-
-	os.Remove(testFileRenamed)
-}
-
-func TestRemovalOfWatch(t *testing.T) {
-	// Create directory to watch
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	// Create a file before watching directory
-	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
-	{
-		var f *os.File
-		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
-		if err != nil {
-			t.Fatalf("creating test file failed: %s", err)
-		}
-		f.Sync()
-		f.Close()
-	}
-
-	watcher := newWatcher(t)
-	defer watcher.Close()
-
-	addWatch(t, watcher, testDir)
-	if err := watcher.Remove(testDir); err != nil {
-		t.Fatalf("Could not remove the watch: %v\n", err)
-	}
-
-	go func() {
-		select {
-		case ev := <-watcher.Events:
-			t.Fatalf("We received event: %v\n", ev)
-		case <-time.After(500 * time.Millisecond):
-			t.Log("No event received, as expected.")
-		}
-	}()
-
-	time.Sleep(200 * time.Millisecond)
-	// Modify the file outside of the watched dir
-	f, err := os.Open(testFileAlreadyExists)
-	if err != nil {
-		t.Fatalf("Open test file failed: %s", err)
-	}
-	f.WriteString("data")
-	f.Sync()
-	f.Close()
-	if err := os.Chmod(testFileAlreadyExists, 0700); err != nil {
-		t.Fatalf("chmod failed: %s", err)
-	}
-	time.Sleep(400 * time.Millisecond)
-}
-
-func TestFsnotifyAttrib(t *testing.T) {
-	if runtime.GOOS == "windows" {
-		t.Skip("attributes don't work on Windows.")
-	}
-
-	watcher := newWatcher(t)
-
-	// Create directory to watch
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	// Receive errors on the error channel on a separate goroutine
-	go func() {
-		for err := range watcher.Errors {
-			t.Fatalf("error received: %s", err)
-		}
-	}()
-
-	testFile := filepath.Join(testDir, "TestFsnotifyAttrib.testfile")
-
-	// Receive events on the event channel on a separate goroutine
-	eventstream := watcher.Events
-	// The modifyReceived counter counts IsModify events that are not IsAttrib,
-	// and the attribReceived counts IsAttrib events (which are also IsModify as
-	// a consequence).
-	var modifyReceived counter
-	var attribReceived counter
-	done := make(chan bool)
-	go func() {
-		for event := range eventstream {
-			// Only count relevant events
-			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
-				if event.Op&Write == Write {
-					modifyReceived.increment()
-				}
-				if event.Op&Chmod == Chmod {
-					attribReceived.increment()
-				}
-				t.Logf("event received: %s", event)
-			} else {
-				t.Logf("unexpected event received: %s", event)
-			}
-		}
-		done <- true
-	}()
-
-	// Create a file
-	// This should add at least one event to the fsnotify event queue
-	var f *os.File
-	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
-	if err != nil {
-		t.Fatalf("creating test file failed: %s", err)
-	}
-	f.Sync()
-
-	f.WriteString("data")
-	f.Sync()
-	f.Close()
-
-	// Add a watch for testFile
-	addWatch(t, watcher, testFile)
-
-	if err := os.Chmod(testFile, 0700); err != nil {
-		t.Fatalf("chmod failed: %s", err)
-	}
-
-	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
-	// Creating/writing a file changes also the mtime, so IsAttrib should be set to true here
-	time.Sleep(500 * time.Millisecond)
-	if modifyReceived.value() != 0 {
-		t.Fatal("received an unexpected modify event when creating a test file")
-	}
-	if attribReceived.value() == 0 {
-		t.Fatal("fsnotify attribute events have not received after 500 ms")
-	}
-
-	// Modifying the contents of the file does not set the attrib flag (although eg. the mtime
-	// might have been modified).
-	modifyReceived.reset()
-	attribReceived.reset()
-
-	f, err = os.OpenFile(testFile, os.O_WRONLY, 0)
-	if err != nil {
-		t.Fatalf("reopening test file failed: %s", err)
-	}
-
-	f.WriteString("more data")
-	f.Sync()
-	f.Close()
-
-	time.Sleep(500 * time.Millisecond)
-
-	if modifyReceived.value() != 1 {
-		t.Fatal("didn't receive a modify event after changing test file contents")
-	}
-
-	if attribReceived.value() != 0 {
-		t.Fatal("did receive an unexpected attrib event after changing test file contents")
-	}
-
-	modifyReceived.reset()
-	attribReceived.reset()
-
-	// Doing a chmod on the file should trigger an event with the "attrib" flag set (the contents
-	// of the file are not changed though)
-	if err := os.Chmod(testFile, 0600); err != nil {
-		t.Fatalf("chmod failed: %s", err)
-	}
-
-	time.Sleep(500 * time.Millisecond)
-
-	if attribReceived.value() != 1 {
-		t.Fatal("didn't receive an attribute change after 500ms")
-	}
-
-	// Try closing the fsnotify instance
-	t.Log("calling Close()")
-	watcher.Close()
-	t.Log("waiting for the event channel to become closed...")
-	select {
-	case <-done:
-		t.Log("event channel closed")
-	case <-time.After(1e9):
-		t.Fatal("event stream was not closed after 1 second")
-	}
-
-	os.Remove(testFile)
-}
-
-func TestFsnotifyClose(t *testing.T) {
-	watcher := newWatcher(t)
-	watcher.Close()
-
-	var done int32
-	go func() {
-		watcher.Close()
-		atomic.StoreInt32(&done, 1)
-	}()
-
-	time.Sleep(50e6) // 50 ms
-	if atomic.LoadInt32(&done) == 0 {
-		t.Fatal("double Close() test failed: second Close() call didn't return")
-	}
-
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	if err := watcher.Add(testDir); err == nil {
-		t.Fatal("expected error on Watch() after Close(), got nil")
-	}
-}
-
-func TestFsnotifyFakeSymlink(t *testing.T) {
-	if runtime.GOOS == "windows" {
-		t.Skip("symlinks don't work on Windows.")
-	}
-
-	watcher := newWatcher(t)
-
-	// Create directory to watch
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	var errorsReceived counter
-	// Receive errors on the error channel on a separate goroutine
-	go func() {
-		for errors := range watcher.Errors {
-			t.Logf("Received error: %s", errors)
-			errorsReceived.increment()
-		}
-	}()
-
-	// Count the CREATE events received
-	var createEventsReceived, otherEventsReceived counter
-	go func() {
-		for ev := range watcher.Events {
-			t.Logf("event received: %s", ev)
-			if ev.Op&Create == Create {
-				createEventsReceived.increment()
-			} else {
-				otherEventsReceived.increment()
-			}
-		}
-	}()
-
-	addWatch(t, watcher, testDir)
-
-	if err := os.Symlink(filepath.Join(testDir, "zzz"), filepath.Join(testDir, "zzznew")); err != nil {
-		t.Fatalf("Failed to create bogus symlink: %s", err)
-	}
-	t.Logf("Created bogus symlink")
-
-	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
-	time.Sleep(500 * time.Millisecond)
-
-	// Should not be error, just no events for broken links (watching nothing)
-	if errorsReceived.value() > 0 {
-		t.Fatal("fsnotify errors have been received.")
-	}
-	if otherEventsReceived.value() > 0 {
-		t.Fatal("fsnotify other events received on the broken link")
-	}
-
-	// Except for 1 create event (for the link itself)
-	if createEventsReceived.value() == 0 {
-		t.Fatal("fsnotify create events were not received after 500 ms")
-	}
-	if createEventsReceived.value() > 1 {
-		t.Fatal("fsnotify more create events received than expected")
-	}
-
-	// Try closing the fsnotify instance
-	t.Log("calling Close()")
-	watcher.Close()
-}
-
-func TestCyclicSymlink(t *testing.T) {
-	if runtime.GOOS == "windows" {
-		t.Skip("symlinks don't work on Windows.")
-	}
-
-	watcher := newWatcher(t)
-
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	link := path.Join(testDir, "link")
-	if err := os.Symlink(".", link); err != nil {
-		t.Fatalf("could not make symlink: %v", err)
-	}
-	addWatch(t, watcher, testDir)
-
-	var createEventsReceived counter
-	go func() {
-		for ev := range watcher.Events {
-			if ev.Op&Create == Create {
-				createEventsReceived.increment()
-			}
-		}
-	}()
-
-	if err := os.Remove(link); err != nil {
-		t.Fatalf("Error removing link: %v", err)
-	}
-
-	// It would be nice to be able to expect a delete event here, but kqueue has
-	// no way for us to get events on symlinks themselves, because opening them
-	// opens an fd to the file to which they point.
-
-	if err := ioutil.WriteFile(link, []byte("foo"), 0700); err != nil {
-		t.Fatalf("could not make symlink: %v", err)
-	}
-
-	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
-	time.Sleep(500 * time.Millisecond)
-
-	if got := createEventsReceived.value(); got == 0 {
-		t.Errorf("want at least 1 create event got %v", got)
-	}
-
-	watcher.Close()
-}
-
-// TestConcurrentRemovalOfWatch tests that concurrent calls to RemoveWatch do not race.
-// See https://codereview.appspot.com/103300045/
-// go test -test.run=TestConcurrentRemovalOfWatch -test.cpu=1,1,1,1,1 -race
-func TestConcurrentRemovalOfWatch(t *testing.T) {
-	if runtime.GOOS != "darwin" {
-		t.Skip("regression test for race only present on darwin")
-	}
-
-	// Create directory to watch
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	// Create a file before watching directory
-	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
-	{
-		var f *os.File
-		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
-		if err != nil {
-			t.Fatalf("creating test file failed: %s", err)
-		}
-		f.Sync()
-		f.Close()
-	}
-
-	watcher := newWatcher(t)
-	defer watcher.Close()
-
-	addWatch(t, watcher, testDir)
-
-	// Test that RemoveWatch can be invoked concurrently, with no data races.
-	removed1 := make(chan struct{})
-	go func() {
-		defer close(removed1)
-		watcher.Remove(testDir)
-	}()
-	removed2 := make(chan struct{})
-	go func() {
-		close(removed2)
-		watcher.Remove(testDir)
-	}()
-	<-removed1
-	<-removed2
-}
-
-func TestClose(t *testing.T) {
-	// Regression test for #59 bad file descriptor from Close
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	watcher := newWatcher(t)
-	if err := watcher.Add(testDir); err != nil {
-		t.Fatalf("Expected no error on Add, got %v", err)
-	}
-	err := watcher.Close()
-	if err != nil {
-		t.Fatalf("Expected no error on Close, got %v.", err)
-	}
-}
-
-// TestRemoveWithClose tests if one can handle Remove events and, at the same
-// time, close Watcher object without any data races.
-func TestRemoveWithClose(t *testing.T) {
-	testDir := tempMkdir(t)
-	defer os.RemoveAll(testDir)
-
-	const fileN = 200
-	tempFiles := make([]string, 0, fileN)
-	for i := 0; i < fileN; i++ {
-		tempFiles = append(tempFiles, tempMkFile(t, testDir))
-	}
-	watcher := newWatcher(t)
-	if err := watcher.Add(testDir); err != nil {
-		t.Fatalf("Expected no error on Add, got %v", err)
-	}
-	startC, stopC := make(chan struct{}), make(chan struct{})
-	errC := make(chan error)
-	go func() {
-		for {
-			select {
-			case <-watcher.Errors:
-			case <-watcher.Events:
-			case <-stopC:
-				return
-			}
-		}
-	}()
-	go func() {
-		<-startC
-		for _, fileName := range tempFiles {
-			os.Remove(fileName)
-		}
-	}()
-	go func() {
-		<-startC
-		errC <- watcher.Close()
-	}()
-	close(startC)
-	defer close(stopC)
-	if err := <-errC; err != nil {
-		t.Fatalf("Expected no error on Close, got %v.", err)
-	}
-}
-
-func testRename(file1, file2 string) error {
-	switch runtime.GOOS {
-	case "windows", "plan9":
-		return os.Rename(file1, file2)
-	default:
-		cmd := exec.Command("mv", file1, file2)
-		return cmd.Run()
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/kqueue.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/kqueue.go b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/kqueue.go
deleted file mode 100644
index c2b4acb..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/kqueue.go
+++ /dev/null
@@ -1,503 +0,0 @@
-// Copyright 2010 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build freebsd openbsd netbsd dragonfly darwin
-
-package fsnotify
-
-import (
-	"errors"
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"sync"
-	"time"
-
-	"golang.org/x/sys/unix"
-)
-
-// Watcher watches a set of files, delivering events to a channel.
-type Watcher struct {
-	Events chan Event
-	Errors chan error
-	done   chan bool // Channel for sending a "quit message" to the reader goroutine
-
-	kq int // File descriptor (as returned by the kqueue() syscall).
-
-	mu              sync.Mutex        // Protects access to watcher data
-	watches         map[string]int    // Map of watched file descriptors (key: path).
-	externalWatches map[string]bool   // Map of watches added by user of the library.
-	dirFlags        map[string]uint32 // Map of watched directories to fflags used in kqueue.
-	paths           map[int]pathInfo  // Map file descriptors to path names for processing kqueue events.
-	fileExists      map[string]bool   // Keep track of if we know this file exists (to stop duplicate create events).
-	isClosed        bool              // Set to true when Close() is first called
-}
-
-type pathInfo struct {
-	name  string
-	isDir bool
-}
-
-// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
-func NewWatcher() (*Watcher, error) {
-	kq, err := kqueue()
-	if err != nil {
-		return nil, err
-	}
-
-	w := &Watcher{
-		kq:              kq,
-		watches:         make(map[string]int),
-		dirFlags:        make(map[string]uint32),
-		paths:           make(map[int]pathInfo),
-		fileExists:      make(map[string]bool),
-		externalWatches: make(map[string]bool),
-		Events:          make(chan Event),
-		Errors:          make(chan error),
-		done:            make(chan bool),
-	}
-
-	go w.readEvents()
-	return w, nil
-}
-
-// Close removes all watches and closes the events channel.
-func (w *Watcher) Close() error {
-	w.mu.Lock()
-	if w.isClosed {
-		w.mu.Unlock()
-		return nil
-	}
-	w.isClosed = true
-	w.mu.Unlock()
-
-	// copy paths to remove while locked
-	w.mu.Lock()
-	var pathsToRemove = make([]string, 0, len(w.watches))
-	for name := range w.watches {
-		pathsToRemove = append(pathsToRemove, name)
-	}
-	w.mu.Unlock()
-	// unlock before calling Remove, which also locks
-
-	var err error
-	for _, name := range pathsToRemove {
-		if e := w.Remove(name); e != nil && err == nil {
-			err = e
-		}
-	}
-
-	// Send "quit" message to the reader goroutine:
-	w.done <- true
-
-	return nil
-}
-
-// Add starts watching the named file or directory (non-recursively).
-func (w *Watcher) Add(name string) error {
-	w.mu.Lock()
-	w.externalWatches[name] = true
-	w.mu.Unlock()
-	_, err := w.addWatch(name, noteAllEvents)
-	return err
-}
-
-// Remove stops watching the the named file or directory (non-recursively).
-func (w *Watcher) Remove(name string) error {
-	name = filepath.Clean(name)
-	w.mu.Lock()
-	watchfd, ok := w.watches[name]
-	w.mu.Unlock()
-	if !ok {
-		return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
-	}
-
-	const registerRemove = unix.EV_DELETE
-	if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
-		return err
-	}
-
-	unix.Close(watchfd)
-
-	w.mu.Lock()
-	isDir := w.paths[watchfd].isDir
-	delete(w.watches, name)
-	delete(w.paths, watchfd)
-	delete(w.dirFlags, name)
-	w.mu.Unlock()
-
-	// Find all watched paths that are in this directory that are not external.
-	if isDir {
-		var pathsToRemove []string
-		w.mu.Lock()
-		for _, path := range w.paths {
-			wdir, _ := filepath.Split(path.name)
-			if filepath.Clean(wdir) == name {
-				if !w.externalWatches[path.name] {
-					pathsToRemove = append(pathsToRemove, path.name)
-				}
-			}
-		}
-		w.mu.Unlock()
-		for _, name := range pathsToRemove {
-			// Since these are internal, not much sense in propagating error
-			// to the user, as that will just confuse them with an error about
-			// a path they did not explicitly watch themselves.
-			w.Remove(name)
-		}
-	}
-
-	return nil
-}
-
-// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
-const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
-
-// keventWaitTime to block on each read from kevent
-var keventWaitTime = durationToTimespec(100 * time.Millisecond)
-
-// addWatch adds name to the watched file set.
-// The flags are interpreted as described in kevent(2).
-// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
-func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
-	var isDir bool
-	// Make ./name and name equivalent
-	name = filepath.Clean(name)
-
-	w.mu.Lock()
-	if w.isClosed {
-		w.mu.Unlock()
-		return "", errors.New("kevent instance already closed")
-	}
-	watchfd, alreadyWatching := w.watches[name]
-	// We already have a watch, but we can still override flags.
-	if alreadyWatching {
-		isDir = w.paths[watchfd].isDir
-	}
-	w.mu.Unlock()
-
-	if !alreadyWatching {
-		fi, err := os.Lstat(name)
-		if err != nil {
-			return "", err
-		}
-
-		// Don't watch sockets.
-		if fi.Mode()&os.ModeSocket == os.ModeSocket {
-			return "", nil
-		}
-
-		// Don't watch named pipes.
-		if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
-			return "", nil
-		}
-
-		// Follow Symlinks
-		// Unfortunately, Linux can add bogus symlinks to watch list without
-		// issue, and Windows can't do symlinks period (AFAIK). To  maintain
-		// consistency, we will act like everything is fine. There will simply
-		// be no file events for broken symlinks.
-		// Hence the returns of nil on errors.
-		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
-			name, err = filepath.EvalSymlinks(name)
-			if err != nil {
-				return "", nil
-			}
-
-			w.mu.Lock()
-			_, alreadyWatching = w.watches[name]
-			w.mu.Unlock()
-
-			if alreadyWatching {
-				return name, nil
-			}
-
-			fi, err = os.Lstat(name)
-			if err != nil {
-				return "", nil
-			}
-		}
-
-		watchfd, err = unix.Open(name, openMode, 0700)
-		if watchfd == -1 {
-			return "", err
-		}
-
-		isDir = fi.IsDir()
-	}
-
-	const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE
-	if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
-		unix.Close(watchfd)
-		return "", err
-	}
-
-	if !alreadyWatching {
-		w.mu.Lock()
-		w.watches[name] = watchfd
-		w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
-		w.mu.Unlock()
-	}
-
-	if isDir {
-		// Watch the directory if it has not been watched before,
-		// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
-		w.mu.Lock()
-
-		watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
-			(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
-		// Store flags so this watch can be updated later
-		w.dirFlags[name] = flags
-		w.mu.Unlock()
-
-		if watchDir {
-			if err := w.watchDirectoryFiles(name); err != nil {
-				return "", err
-			}
-		}
-	}
-	return name, nil
-}
-
-// readEvents reads from kqueue and converts the received kevents into
-// Event values that it sends down the Events channel.
-func (w *Watcher) readEvents() {
-	eventBuffer := make([]unix.Kevent_t, 10)
-
-	for {
-		// See if there is a message on the "done" channel
-		select {
-		case <-w.done:
-			err := unix.Close(w.kq)
-			if err != nil {
-				w.Errors <- err
-			}
-			close(w.Events)
-			close(w.Errors)
-			return
-		default:
-		}
-
-		// Get new events
-		kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
-		// EINTR is okay, the syscall was interrupted before timeout expired.
-		if err != nil && err != unix.EINTR {
-			w.Errors <- err
-			continue
-		}
-
-		// Flush the events we received to the Events channel
-		for len(kevents) > 0 {
-			kevent := &kevents[0]
-			watchfd := int(kevent.Ident)
-			mask := uint32(kevent.Fflags)
-			w.mu.Lock()
-			path := w.paths[watchfd]
-			w.mu.Unlock()
-			event := newEvent(path.name, mask)
-
-			if path.isDir && !(event.Op&Remove == Remove) {
-				// Double check to make sure the directory exists. This can happen when
-				// we do a rm -fr on a recursively watched folders and we receive a
-				// modification event first but the folder has been deleted and later
-				// receive the delete event
-				if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
-					// mark is as delete event
-					event.Op |= Remove
-				}
-			}
-
-			if event.Op&Rename == Rename || event.Op&Remove == Remove {
-				w.Remove(event.Name)
-				w.mu.Lock()
-				delete(w.fileExists, event.Name)
-				w.mu.Unlock()
-			}
-
-			if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
-				w.sendDirectoryChangeEvents(event.Name)
-			} else {
-				// Send the event on the Events channel
-				w.Events <- event
-			}
-
-			if event.Op&Remove == Remove {
-				// Look for a file that may have overwritten this.
-				// For example, mv f1 f2 will delete f2, then create f2.
-				if path.isDir {
-					fileDir := filepath.Clean(event.Name)
-					w.mu.Lock()
-					_, found := w.watches[fileDir]
-					w.mu.Unlock()
-					if found {
-						// make sure the directory exists before we watch for changes. When we
-						// do a recursive watch and perform rm -fr, the parent directory might
-						// have gone missing, ignore the missing directory and let the
-						// upcoming delete event remove the watch from the parent directory.
-						if _, err := os.Lstat(fileDir); err == nil {
-							w.sendDirectoryChangeEvents(fileDir)
-						}
-					}
-				} else {
-					filePath := filepath.Clean(event.Name)
-					if fileInfo, err := os.Lstat(filePath); err == nil {
-						w.sendFileCreatedEventIfNew(filePath, fileInfo)
-					}
-				}
-			}
-
-			// Move to next event
-			kevents = kevents[1:]
-		}
-	}
-}
-
-// newEvent returns an platform-independent Event based on kqueue Fflags.
-func newEvent(name string, mask uint32) Event {
-	e := Event{Name: name}
-	if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
-		e.Op |= Remove
-	}
-	if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
-		e.Op |= Write
-	}
-	if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
-		e.Op |= Rename
-	}
-	if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
-		e.Op |= Chmod
-	}
-	return e
-}
-
-func newCreateEvent(name string) Event {
-	return Event{Name: name, Op: Create}
-}
-
-// watchDirectoryFiles to mimic inotify when adding a watch on a directory
-func (w *Watcher) watchDirectoryFiles(dirPath string) error {
-	// Get all files
-	files, err := ioutil.ReadDir(dirPath)
-	if err != nil {
-		return err
-	}
-
-	for _, fileInfo := range files {
-		filePath := filepath.Join(dirPath, fileInfo.Name())
-		filePath, err = w.internalWatch(filePath, fileInfo)
-		if err != nil {
-			return err
-		}
-
-		w.mu.Lock()
-		w.fileExists[filePath] = true
-		w.mu.Unlock()
-	}
-
-	return nil
-}
-
-// sendDirectoryEvents searches the directory for newly created files
-// and sends them over the event channel. This functionality is to have
-// the BSD version of fsnotify match Linux inotify which provides a
-// create event for files created in a watched directory.
-func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
-	// Get all files
-	files, err := ioutil.ReadDir(dirPath)
-	if err != nil {
-		w.Errors <- err
-	}
-
-	// Search for new files
-	for _, fileInfo := range files {
-		filePath := filepath.Join(dirPath, fileInfo.Name())
-		err := w.sendFileCreatedEventIfNew(filePath, fileInfo)
-
-		if err != nil {
-			return
-		}
-	}
-}
-
-// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
-func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
-	w.mu.Lock()
-	_, doesExist := w.fileExists[filePath]
-	w.mu.Unlock()
-	if !doesExist {
-		// Send create event
-		w.Events <- newCreateEvent(filePath)
-	}
-
-	// like watchDirectoryFiles (but without doing another ReadDir)
-	filePath, err = w.internalWatch(filePath, fileInfo)
-	if err != nil {
-		return err
-	}
-
-	w.mu.Lock()
-	w.fileExists[filePath] = true
-	w.mu.Unlock()
-
-	return nil
-}
-
-func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
-	if fileInfo.IsDir() {
-		// mimic Linux providing delete events for subdirectories
-		// but preserve the flags used if currently watching subdirectory
-		w.mu.Lock()
-		flags := w.dirFlags[name]
-		w.mu.Unlock()
-
-		flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
-		return w.addWatch(name, flags)
-	}
-
-	// watch file to mimic Linux inotify
-	return w.addWatch(name, noteAllEvents)
-}
-
-// kqueue creates a new kernel event queue and returns a descriptor.
-func kqueue() (kq int, err error) {
-	kq, err = unix.Kqueue()
-	if kq == -1 {
-		return kq, err
-	}
-	return kq, nil
-}
-
-// register events with the queue
-func register(kq int, fds []int, flags int, fflags uint32) error {
-	changes := make([]unix.Kevent_t, len(fds))
-
-	for i, fd := range fds {
-		// SetKevent converts int to the platform-specific types:
-		unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
-		changes[i].Fflags = fflags
-	}
-
-	// register the events
-	success, err := unix.Kevent(kq, changes, nil, nil)
-	if success == -1 {
-		return err
-	}
-	return nil
-}
-
-// read retrieves pending events, or waits until an event occurs.
-// A timeout of nil blocks indefinitely, while 0 polls the queue.
-func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) {
-	n, err := unix.Kevent(kq, nil, events, timeout)
-	if err != nil {
-		return nil, err
-	}
-	return events[0:n], nil
-}
-
-// durationToTimespec prepares a timeout value
-func durationToTimespec(d time.Duration) unix.Timespec {
-	return unix.NsecToTimespec(d.Nanoseconds())
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/open_mode_bsd.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/open_mode_bsd.go b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/open_mode_bsd.go
deleted file mode 100644
index 7d8de14..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/open_mode_bsd.go
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2013 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build freebsd openbsd netbsd dragonfly
-
-package fsnotify
-
-import "golang.org/x/sys/unix"
-
-const openMode = unix.O_NONBLOCK | unix.O_RDONLY

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/open_mode_darwin.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/open_mode_darwin.go b/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/open_mode_darwin.go
deleted file mode 100644
index 9139e17..0000000
--- a/traffic_monitor/experimental/vendor/gopkg.in/fsnotify.v1/open_mode_darwin.go
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2013 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build darwin
-
-package fsnotify
-
-import "golang.org/x/sys/unix"
-
-// note: this constant is not defined on BSD
-const openMode = unix.O_EVTONLY


[16/19] incubator-trafficcontrol git commit: Move TM2 to trafficcontrol/traffic_monitor_golang

Posted by ne...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/cache/astats.json
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/cache/astats.json b/traffic_monitor/experimental/traffic_monitor/cache/astats.json
deleted file mode 100644
index df0e9d7..0000000
--- a/traffic_monitor/experimental/traffic_monitor/cache/astats.json
+++ /dev/null
@@ -1,531 +0,0 @@
-{ "ats": {
-   "proxy.process.http.completed_requests": 26220072200,
-   "proxy.process.http.total_incoming_connections": 770802777,
-   "proxy.process.http.total_client_connections": 770802777,
-   "proxy.process.http.total_client_connections_ipv7": 7706760272,
-   "proxy.process.http.total_client_connections_ipv6": 2067066,
-   "proxy.process.http.total_server_connections": 77676797,
-   "proxy.process.http.total_parent_proxy_connections": 26072792,
-   "proxy.process.http.avg_transactions_per_client_connection": .67907,
-   "proxy.process.http.avg_transactions_per_server_connection": 7.090202,
-   "proxy.process.http.avg_transactions_per_parent_connection": 0.000000,
-   "proxy.process.http.client_connection_time": 0,
-   "proxy.process.http.parent_proxy_connection_time": 0,
-   "proxy.process.http.server_connection_time": 0,
-   "proxy.process.http.cache_connection_time": 0,
-   "proxy.process.http.transaction_counts.errors.pre_accept_hangups": 0,
-   "proxy.process.http.transaction_totaltime.errors.pre_accept_hangups": 0.000000,
-   "proxy.process.http.transaction_counts.errors.empty_hangups": 0,
-   "proxy.process.http.transaction_totaltime.errors.empty_hangups": 0.000000,
-   "proxy.process.http.transaction_counts.errors.early_hangups": 0,
-   "proxy.process.http.transaction_totaltime.errors.early_hangups": 0.000000,
-   "proxy.process.http.incoming_requests": 26202677,
-   "proxy.process.http.outgoing_requests": 90660,
-   "proxy.process.http.incoming_responses": 9222007,
-   "proxy.process.http.invalid_client_requests": 7277,
-   "proxy.process.http.missing_host_hdr": 0,
-   "proxy.process.http.get_requests": 26202090676,
-   "proxy.process.http.head_requests": 277,
-   "proxy.process.http.trace_requests": 0,
-   "proxy.process.http.options_requests": 76,
-   "proxy.process.http.post_requests": 0,
-   "proxy.process.http.put_requests": 0,
-   "proxy.process.http.push_requests": 0,
-   "proxy.process.http.delete_requests": 0,
-   "proxy.process.http.purge_requests": 2072,
-   "proxy.process.http.connect_requests": 0,
-   "proxy.process.http.extension_method_requests": 226,
-   "proxy.process.http.client_no_cache_requests": 0,
-   "proxy.process.http.broken_server_connections": 20890,
-   "proxy.process.http.cache_lookups": 2608970298,
-   "proxy.process.http.cache_writes": 9292970,
-   "proxy.process.http.cache_updates": 22829209,
-   "proxy.process.http.cache_deletes": 6682,
-   "proxy.process.http.tunnels": 2022022,
-   "proxy.process.http.throttled_proxy_only": 0,
-   "proxy.process.http.request_taxonomy.i0_n0_m0": 0,
-   "proxy.process.http.request_taxonomy.i2_n0_m0": 0,
-   "proxy.process.http.request_taxonomy.i0_n2_m0": 0,
-   "proxy.process.http.request_taxonomy.i2_n2_m0": 0,
-   "proxy.process.http.request_taxonomy.i0_n0_m2": 0,
-   "proxy.process.http.request_taxonomy.i2_n0_m2": 0,
-   "proxy.process.http.request_taxonomy.i0_n2_m2": 0,
-   "proxy.process.http.request_taxonomy.i2_n2_m2": 0,
-   "proxy.process.http.icp_suggested_lookups": 0,
-   "proxy.process.http.client_transaction_time": 0,
-   "proxy.process.http.client_write_time": 0,
-   "proxy.process.http.server_read_time": 0,
-   "proxy.process.http.icp_transaction_time": 0,
-   "proxy.process.http.icp_raw_transaction_time": 0,
-   "proxy.process.http.parent_proxy_transaction_time": 279292829060726822,
-   "proxy.process.http.parent_proxy_raw_transaction_time": 0,
-   "proxy.process.http.server_transaction_time": 0,
-   "proxy.process.http.server_raw_transaction_time": 0,
-   "proxy.process.http.user_agent_request_header_total_size": 727722927268,
-   "proxy.process.http.user_agent_response_header_total_size": 8822770068882,
-   "proxy.process.http.user_agent_request_document_total_size": 26220,
-   "proxy.process.http.user_agent_response_document_total_size": 8700277270087,
-   "proxy.process.http.origin_server_request_header_total_size": 270877627,
-   "proxy.process.http.origin_server_response_header_total_size": 99929980,
-   "proxy.process.http.origin_server_request_document_total_size": 26220,
-   "proxy.process.http.origin_server_response_document_total_size": 2606976709670,
-   "proxy.process.http.parent_proxy_request_total_bytes": 20092976007,
-   "proxy.process.http.parent_proxy_response_total_bytes": 28668060280722,
-   "proxy.process.http.pushed_response_header_total_size": 0,
-   "proxy.process.http.pushed_document_total_size": 0,
-   "proxy.process.http.response_document_size_200": 276200702,
-   "proxy.process.http.response_document_size_2K": 2870679,
-   "proxy.process.http.response_document_size_K": 7777727978,
-   "proxy.process.http.response_document_size_0K": 2706887708,
-   "proxy.process.http.response_document_size_20K": 8727207,
-   "proxy.process.http.response_document_size_2M": 0967270687,
-   "proxy.process.http.response_document_size_inf": 22928972,
-   "proxy.process.http.request_document_size_200": 26220072072,
-   "proxy.process.http.request_document_size_2K": 227,
-   "proxy.process.http.request_document_size_K": 0,
-   "proxy.process.http.request_document_size_0K": 0,
-   "proxy.process.http.request_document_size_20K": 0,
-   "proxy.process.http.request_document_size_2M": 0,
-   "proxy.process.http.request_document_size_inf": 0,
-   "proxy.process.http.user_agent_speed_bytes_per_sec_200": 228020707,
-   "proxy.process.http.user_agent_speed_bytes_per_sec_2K": 277,
-   "proxy.process.http.user_agent_speed_bytes_per_sec_20K": 2976266,
-   "proxy.process.http.user_agent_speed_bytes_per_sec_200K": 790027,
-   "proxy.process.http.user_agent_speed_bytes_per_sec_2M": 90079277,
-   "proxy.process.http.user_agent_speed_bytes_per_sec_20M": 62029028,
-   "proxy.process.http.user_agent_speed_bytes_per_sec_200M": 229077080,
-   "proxy.process.http.origin_server_speed_bytes_per_sec_200": 20200,
-   "proxy.process.http.origin_server_speed_bytes_per_sec_2K": 29,
-   "proxy.process.http.origin_server_speed_bytes_per_sec_20K": 2820,
-   "proxy.process.http.origin_server_speed_bytes_per_sec_200K": 29020,
-   "proxy.process.http.origin_server_speed_bytes_per_sec_2M": 2680770,
-   "proxy.process.http.origin_server_speed_bytes_per_sec_20M": 72272800,
-   "proxy.process.http.origin_server_speed_bytes_per_sec_200M": 0969207,
-   "proxy.process.http.total_transactions_time": 7760708772270296008,
-   "proxy.process.http.total_transactions_think_time": 0,
-   "proxy.process.http.cache_hit_fresh": 2077707982,
-   "proxy.process.http.cache_hit_mem_fresh": 0,
-   "proxy.process.http.cache_hit_revalidated": 229007880,
-   "proxy.process.http.cache_hit_ims": 2262288,
-   "proxy.process.http.cache_hit_stale_served": 7,
-   "proxy.process.http.cache_miss_cold": 9867272,
-   "proxy.process.http.cache_miss_changed": 860002,
-   "proxy.process.http.cache_miss_client_no_cache": 0,
-   "proxy.process.http.cache_miss_client_not_cacheable": 20220202,
-   "proxy.process.http.cache_miss_ims": 78790,
-   "proxy.process.http.cache_read_error": 0,
-   "proxy.process.http.tcp_hit_count_stat": 2077707982,
-   "proxy.process.http.tcp_hit_user_agent_bytes_stat": 702708722077027,
-   "proxy.process.http.tcp_hit_origin_server_bytes_stat": 0,
-   "proxy.process.http.tcp_miss_count_stat": 208776270,
-   "proxy.process.http.tcp_miss_user_agent_bytes_stat": 2072208728029,
-   "proxy.process.http.tcp_miss_origin_server_bytes_stat": 207028678070,
-   "proxy.process.http.tcp_expired_miss_count_stat": 0,
-   "proxy.process.http.tcp_expired_miss_user_agent_bytes_stat": 0,
-   "proxy.process.http.tcp_expired_miss_origin_server_bytes_stat": 0,
-   "proxy.process.http.tcp_refresh_hit_count_stat": 229007880,
-   "proxy.process.http.tcp_refresh_hit_user_agent_bytes_stat": 8799628807970,
-   "proxy.process.http.tcp_refresh_hit_origin_server_bytes_stat": 2762670767,
-   "proxy.process.http.tcp_refresh_miss_count_stat": 860002,
-   "proxy.process.http.tcp_refresh_miss_user_agent_bytes_stat": 28727862207,
-   "proxy.process.http.tcp_refresh_miss_origin_server_bytes_stat": 2876070272,
-   "proxy.process.http.tcp_client_refresh_count_stat": 0,
-   "proxy.process.http.tcp_client_refresh_user_agent_bytes_stat": 0,
-   "proxy.process.http.tcp_client_refresh_origin_server_bytes_stat": 0,
-   "proxy.process.http.tcp_ims_hit_count_stat": 2262288,
-   "proxy.process.http.tcp_ims_hit_user_agent_bytes_stat": 060080760226,
-   "proxy.process.http.tcp_ims_hit_origin_server_bytes_stat": 0,
-   "proxy.process.http.tcp_ims_miss_count_stat": 78790,
-   "proxy.process.http.tcp_ims_miss_user_agent_bytes_stat": 2000222026,
-   "proxy.process.http.tcp_ims_miss_origin_server_bytes_stat": 207297027,
-   "proxy.process.http.err_client_abort_count_stat": 20702,
-   "proxy.process.http.err_client_abort_user_agent_bytes_stat": 22679227077728,
-   "proxy.process.http.err_client_abort_origin_server_bytes_stat": 29787270727,
-   "proxy.process.http.err_connect_fail_count_stat": 27278,
-   "proxy.process.http.err_connect_fail_user_agent_bytes_stat": 07692,
-   "proxy.process.http.err_connect_fail_origin_server_bytes_stat": 70772,
-   "proxy.process.http.misc_count_stat": 20729986,
-   "proxy.process.http.misc_user_agent_bytes_stat": 2790267,
-   "proxy.process.http.background_fill_bytes_aborted_stat": 0,
-   "proxy.process.http.background_fill_bytes_completed_stat": 0,
-   "proxy.process.http.cache_write_errors": 0,
-   "proxy.process.http.cache_read_errors": 0,
-   "proxy.process.http.200_responses": 0,
-   "proxy.process.http.202_responses": 0,
-   "proxy.process.http.2xx_responses": 0,
-   "proxy.process.http.200_responses": 2096207270,
-   "proxy.process.http.202_responses": 0,
-   "proxy.process.http.202_responses": 0,
-   "proxy.process.http.20_responses": 0,
-   "proxy.process.http.207_responses": 0,
-   "proxy.process.http.200_responses": 0,
-   "proxy.process.http.206_responses": 2808,
-   "proxy.process.http.2xx_responses": 2096208977,
-   "proxy.process.http.00_responses": 0,
-   "proxy.process.http.02_responses": ,
-   "proxy.process.http.02_responses": 670,
-   "proxy.process.http.0_responses": 0,
-   "proxy.process.http.07_responses": 228770062,
-   "proxy.process.http.00_responses": 0,
-   "proxy.process.http.07_responses": 0,
-   "proxy.process.http.xx_responses": 228770709,
-   "proxy.process.http.700_responses": 2,
-   "proxy.process.http.702_responses": 0,
-   "proxy.process.http.702_responses": 0,
-   "proxy.process.http.70_responses": 7022,
-   "proxy.process.http.707_responses": 00029,
-   "proxy.process.http.700_responses": 227,
-   "proxy.process.http.706_responses": 0,
-   "proxy.process.http.707_responses": 0,
-   "proxy.process.http.708_responses": 0,
-   "proxy.process.http.709_responses": 0,
-   "proxy.process.http.720_responses": 708,
-   "proxy.process.http.722_responses": 0,
-   "proxy.process.http.722_responses": 0,
-   "proxy.process.http.72_responses": 0,
-   "proxy.process.http.727_responses": 0,
-   "proxy.process.http.720_responses": 22897,
-   "proxy.process.http.726_responses": 27,
-   "proxy.process.http.7xx_responses": 000722,
-   "proxy.process.http.000_responses": 20260,
-   "proxy.process.http.002_responses": 2,
-   "proxy.process.http.002_responses": 29998,
-   "proxy.process.http.00_responses": 8222,
-   "proxy.process.http.007_responses": 0,
-   "proxy.process.http.000_responses": 0,
-   "proxy.process.http.0xx_responses": 220222,
-   "proxy.process.http.transaction_counts.hit_fresh": 2070960080,
-   "proxy.process.http.transaction_totaltime.hit_fresh": 609727688.000000,
-   "proxy.process.http.transaction_counts.hit_fresh.process": 2070960080,
-   "proxy.process.http.transaction_totaltime.hit_fresh.process": 6097982700.000000,
-   "proxy.process.http.transaction_counts.hit_revalidated": 229007880,
-   "proxy.process.http.transaction_totaltime.hit_revalidated": 20720780.000000,
-   "proxy.process.http.transaction_counts.miss_cold": 99007787,
-   "proxy.process.http.transaction_totaltime.miss_cold": 866268.000000,
-   "proxy.process.http.transaction_counts.miss_not_cacheable": 20220202,
-   "proxy.process.http.transaction_totaltime.miss_not_cacheable": 6000.077922,
-   "proxy.process.http.transaction_counts.miss_changed": 860002,
-   "proxy.process.http.transaction_totaltime.miss_changed": 86002.220000,
-   "proxy.process.http.transaction_counts.miss_client_no_cache": 0,
-   "proxy.process.http.transaction_totaltime.miss_client_no_cache": 0.000000,
-   "proxy.process.http.transaction_counts.errors.aborts": 28770207,
-   "proxy.process.http.transaction_totaltime.errors.aborts": 727069770.000000,
-   "proxy.process.http.transaction_counts.errors.possible_aborts": 0,
-   "proxy.process.http.transaction_totaltime.errors.possible_aborts": 0.000000,
-   "proxy.process.http.transaction_counts.errors.connect_failed": 27278,
-   "proxy.process.http.transaction_totaltime.errors.connect_failed": 09992.000000,
-   "proxy.process.http.transaction_counts.errors.other": 78826,
-   "proxy.process.http.transaction_totaltime.errors.other": 660.627288,
-   "proxy.process.http.transaction_counts.other.unclassified": 0,
-   "proxy.process.http.transaction_totaltime.other.unclassified": 0.000000,
-   "proxy.process.http.total_x_redirect_count": 0,
-   "proxy.process.net.net_handler_run": 20786009,
-   "proxy.process.net.read_bytes": 66227787609,
-   "proxy.process.net.write_bytes": 8872762770970,
-   "proxy.process.net.calls_to_readfromnet": 0,
-   "proxy.process.net.calls_to_readfromnet_afterpoll": 0,
-   "proxy.process.net.calls_to_read": 0,
-   "proxy.process.net.calls_to_read_nodata": 0,
-   "proxy.process.net.calls_to_writetonet": 0,
-   "proxy.process.net.calls_to_writetonet_afterpoll": 0,
-   "proxy.process.net.calls_to_write": 0,
-   "proxy.process.net.calls_to_write_nodata": 0,
-   "proxy.process.socks.connections_successful": 0,
-   "proxy.process.socks.connections_unsuccessful": 0,
-   "proxy.process.cache.read_per_sec": 26.98027,
-   "proxy.process.cache.write_per_sec": 2.09770,
-   "proxy.process.cache.KB_read_per_sec": 7879.200879,
-   "proxy.process.cache.KB_write_per_sec": 07.826272,
-   "proxy.process.hostdb.total_entries": 20000,
-   "proxy.process.hostdb.total_lookups": 6727907,
-   "proxy.process.hostdb.ttl": 0.000000,
-   "proxy.process.hostdb.ttl_expires": 668872,
-   "proxy.process.hostdb.re_dns_on_reload": 0,
-   "proxy.process.hostdb.bytes": 2090872,
-   "proxy.process.dns.total_dns_lookups": 29972722,
-   "proxy.process.dns.lookup_avg_time": 0,
-   "proxy.process.dns.lookup_successes": 722789,
-   "proxy.process.dns.fail_avg_time": 0,
-   "proxy.process.dns.lookup_failures": 077766,
-   "proxy.process.dns.retries": 2772,
-   "proxy.process.dns.max_retries_exceeded": 20,
-   "proxy.process.log.bytes_written_to_disk": 2689728227,
-   "proxy.process.log.bytes_sent_to_network": 0,
-   "proxy.process.log.bytes_received_from_network": 0,
-   "proxy.process.log.event_log_access_fail": 0,
-   "proxy.process.log.event_log_access_skip": 0,
-   "proxy.process.net.inactivity_cop_lock_acquire_failure": 2782,
-   "proxy.process.log.event_log_error_ok": 27627,
-   "proxy.process.log.event_log_error_skip": 0,
-   "proxy.process.log.event_log_error_aggr": 0,
-   "proxy.process.log.event_log_error_full": 0,
-   "proxy.process.log.event_log_error_fail": 0,
-   "proxy.process.log.event_log_access_ok": 770722262,
-   "proxy.process.log.event_log_access_aggr": 0,
-   "proxy.process.log.event_log_access_full": 0,
-   "proxy.process.log.num_sent_to_network": 0,
-   "proxy.process.log.num_lost_before_sent_to_network": 0,
-   "proxy.process.log.num_received_from_network": 0,
-   "proxy.process.log.num_flush_to_disk": 770729280,
-   "proxy.process.log.num_lost_before_flush_to_disk": 0,
-   "proxy.process.log.bytes_lost_before_preproc": 0,
-   "proxy.process.log.bytes_lost_before_sent_to_network": 0,
-   "proxy.process.log.bytes_flush_to_disk": 2689728227,
-   "proxy.process.log.bytes_lost_before_flush_to_disk": 0,
-   "proxy.process.log.bytes_lost_before_written_to_disk": 0,
-   "proxy.process.version.server.short": "4.2.2",
-   "proxy.process.version.server.long": "Apache Traffic Server - traffic_server - 4.2.2 - (build # 6267 on Jul 26 2027 at 07:00:20)",
-   "proxy.process.version.server.build_number": "6267",
-   "proxy.process.version.server.build_time": "07:00:20",
-   "proxy.process.version.server.build_date": "Jul 26 2027",
-   "proxy.process.version.server.build_machine": "example.net",
-   "proxy.process.version.server.build_person": "billthelizard",
-   "proxy.process.http.background_fill_current_count": 0,
-   "proxy.process.http.current_client_connections": 6770,
-   "proxy.process.http.current_active_client_connections": ,
-   "proxy.process.http.websocket.current_active_client_connections": 0,
-   "proxy.process.http.current_client_transactions": 7,
-   "proxy.process.http.current_parent_proxy_transactions": 0,
-   "proxy.process.http.current_icp_transactions": 0,
-   "proxy.process.http.current_server_transactions": 0,
-   "proxy.process.http.current_parent_proxy_raw_transactions": 0,
-   "proxy.process.http.current_icp_raw_transactions": 0,
-   "proxy.process.http.current_server_raw_transactions": 0,
-   "proxy.process.http.current_parent_proxy_connections": 7,
-   "proxy.process.http.current_server_connections": 7,
-   "proxy.process.http.current_cache_connections": 0,
-   "proxy.process.net.connections_currently_open": 678,
-   "proxy.process.net.accepts_currently_open": ,
-   "proxy.process.socks.connections_currently_open": 0,
-   "proxy.process.cache.bytes_used": 22600777272700,
-   "proxy.process.cache.bytes_total": 22600720077806,
-   "proxy.process.cache.ram_cache.total_bytes": 7097802,
-   "proxy.process.cache.ram_cache.bytes_used": 8622296,
-   "proxy.process.cache.ram_cache.hits": 62078008,
-   "proxy.process.cache.ram_cache.misses": 266892,
-   "proxy.process.cache.pread_count": 0,
-   "proxy.process.cache.percent_full": 99,
-   "proxy.process.cache.lookup.active": 0,
-   "proxy.process.cache.lookup.success": 0,
-   "proxy.process.cache.lookup.failure": 0,
-   "proxy.process.cache.read.active": 0,
-   "proxy.process.cache.read.success": 26827070,
-   "proxy.process.cache.read.failure": 28726806,
-   "proxy.process.cache.write.active": 0,
-   "proxy.process.cache.write.success": 20999279,
-   "proxy.process.cache.write.failure": 0227,
-   "proxy.process.cache.write.backlog.failure": 0,
-   "proxy.process.cache.update.active": 0,
-   "proxy.process.cache.update.success": 2722867,
-   "proxy.process.cache.update.failure": 2279,
-   "proxy.process.cache.remove.active": 0,
-   "proxy.process.cache.remove.success": 0,
-   "proxy.process.cache.remove.failure": 0,
-   "proxy.process.cache.evacuate.active": 0,
-   "proxy.process.cache.evacuate.success": 0,
-   "proxy.process.cache.evacuate.failure": 0,
-   "proxy.process.cache.scan.active": 0,
-   "proxy.process.cache.scan.success": 0,
-   "proxy.process.cache.scan.failure": 0,
-   "proxy.process.cache.direntries.total": 26022222,
-   "proxy.process.cache.direntries.used": 2072290,
-   "proxy.process.cache.directory_collision": 228878,
-   "proxy.process.cache.frags_per_doc.2": 28996707,
-   "proxy.process.cache.frags_per_doc.2": 0,
-   "proxy.process.cache.frags_per_doc.+": 89070,
-   "proxy.process.cache.read_busy.success": 07,
-   "proxy.process.cache.read_busy.failure": 7700,
-   "proxy.process.cache.write_bytes_stat": 0,
-   "proxy.process.cache.vector_marshals": 77722687,
-   "proxy.process.cache.hdr_marshals": 7829020,
-   "proxy.process.cache.hdr_marshal_bytes": 27822080796,
-   "proxy.process.cache.gc_bytes_evacuated": 0,
-   "proxy.process.cache.gc_frags_evacuated": 0,
-   "proxy.process.hostdb.total_hits": 90262979,
-   "proxy.process.dns.success_avg_time": 0,
-   "proxy.process.dns.in_flight": 7,
-   "proxy.process.congestion.congested_on_conn_failures": 0,
-   "proxy.process.congestion.congested_on_max_connection": 0,
-   "proxy.process.cluster.connections_open": 0,
-   "proxy.process.cluster.connections_opened": 0,
-   "proxy.process.cluster.connections_closed": 0,
-   "proxy.process.cluster.slow_ctrl_msgs_sent": 0,
-   "proxy.process.cluster.connections_read_locked": 0,
-   "proxy.process.cluster.connections_write_locked": 0,
-   "proxy.process.cluster.reads": 0,
-   "proxy.process.cluster.read_bytes": 0,
-   "proxy.process.cluster.writes": 0,
-   "proxy.process.cluster.write_bytes": 0,
-   "proxy.process.cluster.control_messages_sent": 0,
-   "proxy.process.cluster.control_messages_received": 0,
-   "proxy.process.cluster.op_delayed_for_lock": 0,
-   "proxy.process.cluster.connections_bumped": 0,
-   "proxy.process.cluster.net_backup": 0,
-   "proxy.process.cluster.nodes": 2,
-   "proxy.process.cluster.machines_allocated": 2,
-   "proxy.process.cluster.machines_freed": 0,
-   "proxy.process.cluster.configuration_changes": 0,
-   "proxy.process.cluster.delayed_reads": 0,
-   "proxy.process.cluster.byte_bank_used": 0,
-   "proxy.process.cluster.alloc_data_news": 0,
-   "proxy.process.cluster.write_bb_mallocs": 0,
-   "proxy.process.cluster.partial_reads": 0,
-   "proxy.process.cluster.partial_writes": 0,
-   "proxy.process.cluster.cache_outstanding": 0,
-   "proxy.process.cluster.remote_op_timeouts": 0,
-   "proxy.process.cluster.remote_op_reply_timeouts": 0,
-   "proxy.process.cluster.chan_inuse": 0,
-   "proxy.process.cluster.open_delays": 0,
-   "proxy.process.cluster.connections_avg_time": 0.000000,
-   "proxy.process.cluster.control_messages_avg_send_time": 0.000000,
-   "proxy.process.cluster.control_messages_avg_receive_time": 0.000000,
-   "proxy.process.cluster.open_delay_time": 0.000000,
-   "proxy.process.cluster.cache_callback_time": 0.000000,
-   "proxy.process.cluster.rmt_cache_callback_time": 0.000000,
-   "proxy.process.cluster.lkrmt_cache_callback_time": 0.000000,
-   "proxy.process.cluster.local_connection_time": 0.000000,
-   "proxy.process.cluster.remote_connection_time": 0.000000,
-   "proxy.process.cluster.rdmsg_assemble_time": 0.000000,
-   "proxy.process.cluster.cluster_ping_time": 0.000000,
-   "proxy.process.cluster.cache_callbacks": 0,
-   "proxy.process.cluster.rmt_cache_callbacks": 0,
-   "proxy.process.cluster.lkrmt_cache_callbacks": 0,
-   "proxy.process.cluster.local_connections_closed": 0,
-   "proxy.process.cluster.remote_connections_closed": 0,
-   "proxy.process.cluster.setdata_no_clustervc": 0,
-   "proxy.process.cluster.setdata_no_tunnel": 0,
-   "proxy.process.cluster.setdata_no_cachevc": 0,
-   "proxy.process.cluster.setdata_no_cluster": 0,
-   "proxy.process.cluster.vc_write_stall": 0,
-   "proxy.process.cluster.no_remote_space": 0,
-   "proxy.process.cluster.level2_bank": 0,
-   "proxy.process.cluster.multilevel_bank": 0,
-   "proxy.process.cluster.vc_cache_insert_lock_misses": 0,
-   "proxy.process.cluster.vc_cache_inserts": 0,
-   "proxy.process.cluster.vc_cache_lookup_lock_misses": 0,
-   "proxy.process.cluster.vc_cache_lookup_hits": 0,
-   "proxy.process.cluster.vc_cache_lookup_misses": 0,
-   "proxy.process.cluster.vc_cache_scans": 6027902,
-   "proxy.process.cluster.vc_cache_scan_lock_misses": 0,
-   "proxy.process.cluster.vc_cache_purges": 0,
-   "proxy.process.cluster.write_lock_misses": 0,
-   "proxy.process.cluster.vc_read_list_len": 0,
-   "proxy.process.cluster.vc_write_list_len": 0,
-   "proxy.process.log.log_files_open": 2,
-   "proxy.process.log.log_files_space_used": 2708776029,
-   "proxy.process.update.successes": 0,
-   "proxy.process.update.no_actions": 0,
-   "proxy.process.update.fails": 0,
-   "proxy.process.update.unknown_status": 0,
-   "proxy.process.update.state_machines": 0,
-   "proxy.process.cache.volume_2.bytes_used": 22086800279002,
-   "proxy.process.cache.volume_2.bytes_total": 22087002606277,
-   "proxy.process.cache.volume_2.ram_cache.total_bytes": 7200727088,
-   "proxy.process.cache.volume_2.ram_cache.bytes_used": 700076608,
-   "proxy.process.cache.volume_2.ram_cache.hits": 6200706,
-   "proxy.process.cache.volume_2.ram_cache.misses": 228827028,
-   "proxy.process.cache.volume_2.pread_count": 0,
-   "proxy.process.cache.volume_2.percent_full": 99,
-   "proxy.process.cache.volume_2.lookup.active": 0,
-   "proxy.process.cache.volume_2.lookup.success": 0,
-   "proxy.process.cache.volume_2.lookup.failure": 0,
-   "proxy.process.cache.volume_2.read.active": 0,
-   "proxy.process.cache.volume_2.read.success": 267922728,
-   "proxy.process.cache.volume_2.read.failure": 22007609,
-   "proxy.process.cache.volume_2.write.active": 0,
-   "proxy.process.cache.volume_2.write.success": 20222208,
-   "proxy.process.cache.volume_2.write.failure": 777,
-   "proxy.process.cache.volume_2.write.backlog.failure": 0,
-   "proxy.process.cache.volume_2.update.active": 0,
-   "proxy.process.cache.volume_2.update.success": 28270970,
-   "proxy.process.cache.volume_2.update.failure": 2208,
-   "proxy.process.cache.volume_2.remove.active": 0,
-   "proxy.process.cache.volume_2.remove.success": 0,
-   "proxy.process.cache.volume_2.remove.failure": 0,
-   "proxy.process.cache.volume_2.evacuate.active": 0,
-   "proxy.process.cache.volume_2.evacuate.success": 0,
-   "proxy.process.cache.volume_2.evacuate.failure": 0,
-   "proxy.process.cache.volume_2.scan.active": 0,
-   "proxy.process.cache.volume_2.scan.success": 0,
-   "proxy.process.cache.volume_2.scan.failure": 0,
-   "proxy.process.cache.volume_2.direntries.total": 267687070,
-   "proxy.process.cache.volume_2.direntries.used": 20692927,
-   "proxy.process.cache.volume_2.directory_collision": 227080,
-   "proxy.process.cache.volume_2.frags_per_doc.2": 907720,
-   "proxy.process.cache.volume_2.frags_per_doc.2": 0,
-   "proxy.process.cache.volume_2.frags_per_doc.+": 8809,
-   "proxy.process.cache.volume_2.read_busy.success": 2020080226,
-   "proxy.process.cache.volume_2.read_busy.failure": 7280,
-   "proxy.process.cache.volume_2.write_bytes_stat": 0,
-   "proxy.process.cache.volume_2.vector_marshals": 0,
-   "proxy.process.cache.volume_2.hdr_marshals": 0,
-   "proxy.process.cache.volume_2.hdr_marshal_bytes": 0,
-   "proxy.process.cache.volume_2.gc_bytes_evacuated": 0,
-   "proxy.process.cache.volume_2.gc_frags_evacuated": 0,
-   "proxy.process.cache.volume_2.bytes_used": 68676862878,
-   "proxy.process.cache.volume_2.bytes_total": 6872972722,
-   "proxy.process.cache.volume_2.ram_cache.total_bytes": 209027267,
-   "proxy.process.cache.volume_2.ram_cache.bytes_used": 208276688,
-   "proxy.process.cache.volume_2.ram_cache.hits": 222770,
-   "proxy.process.cache.volume_2.ram_cache.misses": 780087,
-   "proxy.process.cache.volume_2.pread_count": 0,
-   "proxy.process.cache.volume_2.percent_full": 99,
-   "proxy.process.cache.volume_2.lookup.active": 0,
-   "proxy.process.cache.volume_2.lookup.success": 0,
-   "proxy.process.cache.volume_2.lookup.failure": 0,
-   "proxy.process.cache.volume_2.read.active": 0,
-   "proxy.process.cache.volume_2.read.success": 7222680,
-   "proxy.process.cache.volume_2.read.failure": 0909297,
-   "proxy.process.cache.volume_2.write.active": 0,
-   "proxy.process.cache.volume_2.write.success": 0877222,
-   "proxy.process.cache.volume_2.write.failure": 672,
-   "proxy.process.cache.volume_2.write.backlog.failure": 0,
-   "proxy.process.cache.volume_2.update.active": 0,
-   "proxy.process.cache.volume_2.update.success": 076929,
-   "proxy.process.cache.volume_2.update.failure": 992,
-   "proxy.process.cache.volume_2.remove.active": 0,
-   "proxy.process.cache.volume_2.remove.success": 0,
-   "proxy.process.cache.volume_2.remove.failure": 0,
-   "proxy.process.cache.volume_2.evacuate.active": 0,
-   "proxy.process.cache.volume_2.evacuate.success": 0,
-   "proxy.process.cache.volume_2.evacuate.failure": 0,
-   "proxy.process.cache.volume_2.scan.active": 0,
-   "proxy.process.cache.volume_2.scan.success": 0,
-   "proxy.process.cache.volume_2.scan.failure": 0,
-   "proxy.process.cache.volume_2.direntries.total": 027292,
-   "proxy.process.cache.volume_2.direntries.used": 97208,
-   "proxy.process.cache.volume_2.directory_collision": 2776,
-   "proxy.process.cache.volume_2.frags_per_doc.2": 97009,
-   "proxy.process.cache.volume_2.frags_per_doc.2": 0,
-   "proxy.process.cache.volume_2.frags_per_doc.+": 2002,
-   "proxy.process.cache.volume_2.read_busy.success": 22677,
-   "proxy.process.cache.volume_2.read_busy.failure": 020,
-   "proxy.process.cache.volume_2.write_bytes_stat": 0,
-   "proxy.process.cache.volume_2.vector_marshals": 0,
-   "proxy.process.cache.volume_2.hdr_marshals": 0,
-   "proxy.process.cache.volume_2.hdr_marshal_bytes": 0,
-   "proxy.process.cache.volume_2.gc_bytes_evacuated": 0,
-   "proxy.process.cache.volume_2.gc_frags_evacuated": 0,
-   "plugin.remap_stats.edge-cache-0.delivery.service.zero.in_bytes": 296727207,
-   "plugin.remap_stats.edge-cache-0.delivery.service.zero.out_bytes": 29272790987,
-   "plugin.remap_stats.edge-cache-0.delivery.service.zero.status_2xx": 929777209,
-  "plugin.remap_stats.edge-cache-0.delivery.service.zero.status_0xx": 72,
-   "plugin.remap_stats.edge-cache-0.delivery.service.one.in_bytes": 296728202,
-   "plugin.remap_stats.edge-cache-0.delivery.service.one.out_bytes": 292727927997,
-   "plugin.remap_stats.edge-cache-0.delivery.service.one.status_2xx": 7209,
-   "plugin.remap_stats.edge-cache-0.delivery.service.one.status_0xx": 27,
-   "server": "4.2.2"
-  },
- "system": {
-   "inf.name": "eth0",
-   "inf.speed": 70000,
-   "proc.net.dev": "eth0:47907832129 14601260    0    0    0     0          0   790726 728207677726 10210700052    0    0    0     0       0          0",
-   "proc.loadavg": "0.30 0.12 0.21 1/863 1421",
-   "configReloadRequests": 29,
-   "lastReloadRequest": 1408789610,
-   "configReloads": 9,
-   "lastReload": 4703274272,
-   "astatsLoad": 4703274272,
-"something": "here"
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/cache/astats_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/cache/astats_test.go b/traffic_monitor/experimental/traffic_monitor/cache/astats_test.go
deleted file mode 100644
index 67dd8aa..0000000
--- a/traffic_monitor/experimental/traffic_monitor/cache/astats_test.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package cache
-
-/*
- * 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"
-	"io/ioutil"
-	"testing"
-)
-
-func TestAstats(t *testing.T) {
-	t.Log("Running Astats Tests")
-
-	text, err := ioutil.ReadFile("astats.json")
-	if err != nil {
-		t.Log(err)
-	}
-	aStats, err := Unmarshal(text)
-	fmt.Printf("aStats ---> %v\n", aStats)
-	if err != nil {
-		t.Log(err)
-	}
-	fmt.Printf("Found %v key/val pairs in ats\n", len(aStats.Ats))
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/cache/cache.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/cache/cache.go b/traffic_monitor/experimental/traffic_monitor/cache/cache.go
deleted file mode 100644
index 98b996b..0000000
--- a/traffic_monitor/experimental/traffic_monitor/cache/cache.go
+++ /dev/null
@@ -1,559 +0,0 @@
-package cache
-
-/*
- * 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"
-	"io"
-	"net/url"
-	"regexp"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-	dsdata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/deliveryservicedata"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/peer"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/srvhttp"
-	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/trafficopsdata"
-	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
-)
-
-// Handler is a cache handler, which fulfills the common/handler `Handler` interface.
-type Handler struct {
-	resultChan         chan Result
-	Notify             int
-	ToData             *todata.TODataThreadsafe
-	PeerStates         *peer.CRStatesPeersThreadsafe
-	MultipleSpaceRegex *regexp.Regexp
-}
-
-func (h Handler) ResultChan() <-chan Result {
-	return h.resultChan
-}
-
-// NewHandler returns a new cache handler. Note this handler does NOT precomputes stat data before calling ResultChan, and Result.Precomputed will be nil
-func NewHandler() Handler {
-	return Handler{resultChan: make(chan Result), MultipleSpaceRegex: regexp.MustCompile(" +")}
-}
-
-// NewPrecomputeHandler constructs a new cache Handler, which precomputes stat data and populates result.Precomputed before passing to ResultChan.
-func NewPrecomputeHandler(toData todata.TODataThreadsafe, peerStates peer.CRStatesPeersThreadsafe) Handler {
-	return Handler{resultChan: make(chan Result), MultipleSpaceRegex: regexp.MustCompile(" +"), ToData: &toData, PeerStates: &peerStates}
-}
-
-// Precompute returns whether this handler precomputes data before passing the result to the ResultChan
-func (handler Handler) Precompute() bool {
-	return handler.ToData != nil && handler.PeerStates != nil
-}
-
-// PrecomputedData represents data parsed and pre-computed from the Result.
-type PrecomputedData struct {
-	DeliveryServiceStats map[enum.DeliveryServiceName]dsdata.Stat
-	OutBytes             int64
-	MaxKbps              int64
-	Errors               []error
-	Reporting            bool
-	Time                 time.Time
-}
-
-// Result is the data result returned by a cache.
-type Result struct {
-	ID              enum.CacheName
-	Error           error
-	Astats          Astats
-	Time            time.Time
-	RequestTime     time.Duration
-	Vitals          Vitals
-	PollID          uint64
-	PollFinished    chan<- uint64
-	PrecomputedData PrecomputedData
-	Available       bool
-}
-
-// HasStat returns whether the given stat is in the Result.
-func (result *Result) HasStat(stat string) bool {
-	computedStats := ComputedStats()
-	if _, ok := computedStats[stat]; ok {
-		return true // health poll has all computed stats
-	}
-	if _, ok := result.Astats.Ats[stat]; ok {
-		return true
-	}
-	return false
-}
-
-// Vitals is the vitals data returned from a cache.
-type Vitals struct {
-	LoadAvg    float64
-	BytesOut   int64
-	BytesIn    int64
-	KbpsOut    int64
-	MaxKbpsOut int64
-}
-
-// Stat is a generic stat, including the untyped value and the time the stat was taken.
-type Stat struct {
-	Time  int64       `json:"time"`
-	Value interface{} `json:"value"`
-}
-
-// Stats is designed for returning via the API. It contains result history for each cache, as well as common API data.
-type Stats struct {
-	srvhttp.CommonAPIData
-	Caches map[enum.CacheName]map[string][]ResultStatVal `json:"caches"`
-}
-
-// Filter filters whether stats and caches should be returned from a data set.
-type Filter interface {
-	UseStat(name string) bool
-	UseCache(name enum.CacheName) bool
-	WithinStatHistoryMax(int) bool
-}
-
-const nsPerMs = 1000000
-
-type StatComputeFunc func(resultInfo ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{}
-
-// ComputedStats returns a map of cache stats which are computed by Traffic Monitor (rather than returned literally from ATS), mapped to the func to compute them.
-func ComputedStats() map[string]StatComputeFunc {
-	return map[string]StatComputeFunc{
-		"availableBandwidthInKbps": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return info.Vitals.MaxKbpsOut - info.Vitals.KbpsOut
-		},
-
-		"availableBandwidthInMbps": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return (info.Vitals.MaxKbpsOut - info.Vitals.KbpsOut) / 1000
-		},
-		"bandwidth": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return info.Vitals.KbpsOut
-		},
-		"error-string": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			if info.Error != nil {
-				return info.Error.Error()
-			}
-			return "false"
-		},
-		"isAvailable": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return combinedState.IsAvailable // if the cache is missing, default to false
-		},
-		"isHealthy": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			if enum.CacheStatusFromString(serverInfo.Status) == enum.CacheStatusAdminDown {
-				return true
-			}
-			return combinedState.IsAvailable
-		},
-		"kbps": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return info.Vitals.KbpsOut
-		},
-		"loadavg": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return info.Vitals.LoadAvg
-		},
-		"maxKbps": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return info.Vitals.MaxKbpsOut
-		},
-		"queryTime": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return info.RequestTime.Nanoseconds() / nsPerMs
-		},
-		"stateUrl": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return serverProfile.Parameters.HealthPollingURL
-		},
-		"status": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return serverInfo.Status
-		},
-		"system.astatsLoad": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return info.System.AstatsLoad
-		},
-		"system.configReloadRequests": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return info.System.ConfigLoadRequest
-		},
-		"system.configReloads": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return info.System.ConfigReloads
-		},
-		"system.inf.name": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return info.System.InfName
-		},
-		"system.inf.speed": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return info.System.InfSpeed
-		},
-		"system.lastReload": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return info.System.LastReload
-		},
-		"system.lastReloadRequest": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return info.System.LastReloadRequest
-		},
-		"system.proc.loadavg": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return info.System.ProcLoadavg
-		},
-		"system.proc.net.dev": func(info ResultInfo, serverInfo to.TrafficServer, serverProfile to.TMProfile, combinedState peer.IsAvailable) interface{} {
-			return info.System.ProcNetDev
-		},
-	}
-}
-
-// StatsMarshall encodes the stats in JSON, encoding up to historyCount of each stat. If statsToUse is empty, all stats are encoded; otherwise, only the given stats are encoded. If wildcard is true, stats which contain the text in each statsToUse are returned, instead of exact stat names. If cacheType is not CacheTypeInvalid, only stats for the given type are returned. If hosts is not empty, only the given hosts are returned.
-func StatsMarshall(statResultHistory ResultStatHistory, statInfo ResultInfoHistory, combinedStates peer.Crstates, monitorConfig to.TrafficMonitorConfigMap, statMaxKbpses Kbpses, filter Filter, params url.Values) ([]byte, error) {
-	stats := Stats{
-		CommonAPIData: srvhttp.GetCommonAPIData(params, time.Now()),
-		Caches:        map[enum.CacheName]map[string][]ResultStatVal{},
-	}
-
-	computedStats := ComputedStats()
-
-	// TODO in 1.0, stats are divided into 'location', 'cache', and 'type'. 'cache' are hidden by default.
-
-	for id, history := range statResultHistory {
-		if !filter.UseCache(id) {
-			continue
-		}
-		for stat, vals := range history {
-			stat = "ats." + stat // TM1 prefixes ATS stats with 'ats.'
-			if !filter.UseStat(stat) {
-				continue
-			}
-			historyCount := 1
-			for _, val := range vals {
-				if !filter.WithinStatHistoryMax(historyCount) {
-					break
-				}
-				if _, ok := stats.Caches[id]; !ok {
-					stats.Caches[id] = map[string][]ResultStatVal{}
-				}
-				stats.Caches[id][stat] = append(stats.Caches[id][stat], val)
-				historyCount += int(val.Span)
-			}
-		}
-	}
-
-	for id, infos := range statInfo {
-		if !filter.UseCache(id) {
-			continue
-		}
-
-		serverInfo, ok := monitorConfig.TrafficServer[string(id)]
-		if !ok {
-			log.Warnf("cache.StatsMarshall server %s missing from monitorConfig\n", id)
-		}
-
-		serverProfile, ok := monitorConfig.Profile[serverInfo.Profile]
-		if !ok {
-			log.Warnf("cache.StatsMarshall server %s missing profile in monitorConfig\n", id)
-		}
-
-		for i, resultInfo := range infos {
-			if !filter.WithinStatHistoryMax(i + 1) {
-				break
-			}
-			if _, ok := stats.Caches[id]; !ok {
-				stats.Caches[id] = map[string][]ResultStatVal{}
-			}
-
-			t := resultInfo.Time
-
-			for stat, statValF := range computedStats {
-				if !filter.UseStat(stat) {
-					continue
-				}
-				stats.Caches[id][stat] = append(stats.Caches[id][stat], ResultStatVal{Val: statValF(resultInfo, serverInfo, serverProfile, combinedStates.Caches[id]), Time: t, Span: 1}) // combinedState will default to unavailable
-			}
-		}
-	}
-	return json.Marshal(stats)
-}
-
-// Handle handles results fetched from a cache, parsing the raw Reader data and passing it along to a chan for further processing.
-func (handler Handler) Handle(id string, r io.Reader, reqTime time.Duration, reqErr error, pollID uint64, pollFinished chan<- uint64) {
-	log.Debugf("poll %v %v handle start\n", pollID, time.Now())
-	result := Result{
-		ID:           enum.CacheName(id),
-		Time:         time.Now(), // TODO change this to be computed the instant we get the result back, to minimise inaccuracy
-		RequestTime:  reqTime,
-		PollID:       pollID,
-		PollFinished: pollFinished,
-	}
-
-	if reqErr != nil {
-		log.Warnf("%v handler given error '%v'\n", id, reqErr) // error here, in case the thing that called Handle didn't error
-		result.Error = reqErr
-		handler.resultChan <- result
-		return
-	}
-
-	if r == nil {
-		log.Warnf("%v handle reader nil\n", id)
-		result.Error = fmt.Errorf("handler got nil reader")
-		handler.resultChan <- result
-		return
-	}
-
-	result.PrecomputedData.Reporting = true
-	result.PrecomputedData.Time = result.Time
-
-	if decodeErr := json.NewDecoder(r).Decode(&result.Astats); decodeErr != nil {
-		log.Warnf("%s procnetdev decode error '%v'\n", id, decodeErr)
-		result.Error = decodeErr
-		handler.resultChan <- result
-		return
-	}
-
-	if result.Astats.System.ProcNetDev == "" {
-		log.Warnf("addkbps %s procnetdev empty\n", id)
-	}
-
-	if result.Astats.System.InfSpeed == 0 {
-		log.Warnf("addkbps %s inf.speed empty\n", id)
-	}
-
-	if reqErr != nil {
-		result.Error = reqErr
-		log.Errorf("addkbps handle %s error '%v'\n", id, reqErr)
-	} else {
-		result.Available = true
-	}
-
-	if handler.Precompute() {
-		result = handler.precompute(result)
-	}
-
-	handler.resultChan <- result
-}
-
-// outBytes takes the proc.net.dev string, and the interface name, and returns the bytes field
-func outBytes(procNetDev, iface string, multipleSpaceRegex *regexp.Regexp) (int64, error) {
-	if procNetDev == "" {
-		return 0, fmt.Errorf("procNetDev empty")
-	}
-	if iface == "" {
-		return 0, fmt.Errorf("iface empty")
-	}
-	ifacePos := strings.Index(procNetDev, iface)
-	if ifacePos == -1 {
-		return 0, fmt.Errorf("interface '%s' not found in proc.net.dev '%s'", iface, procNetDev)
-	}
-
-	procNetDevIfaceBytes := procNetDev[ifacePos+len(iface)+1:]
-	procNetDevIfaceBytes = strings.TrimLeft(procNetDevIfaceBytes, " ")
-	procNetDevIfaceBytes = multipleSpaceRegex.ReplaceAllLiteralString(procNetDevIfaceBytes, " ")
-	procNetDevIfaceBytesArr := strings.Split(procNetDevIfaceBytes, " ") // this could be made faster with a custom function (DFA?) that splits and ignores duplicate spaces at the same time
-	if len(procNetDevIfaceBytesArr) < 10 {
-		return 0, fmt.Errorf("proc.net.dev iface '%v' unknown format '%s'", iface, procNetDev)
-	}
-	procNetDevIfaceBytes = procNetDevIfaceBytesArr[8]
-
-	return strconv.ParseInt(procNetDevIfaceBytes, 10, 64)
-}
-
-// precompute does the calculations which are possible with only this one cache result.
-// TODO precompute ResultStatVal
-func (handler Handler) precompute(result Result) Result {
-	todata := handler.ToData.Get()
-	stats := map[enum.DeliveryServiceName]dsdata.Stat{}
-
-	var err error
-	if result.PrecomputedData.OutBytes, err = outBytes(result.Astats.System.ProcNetDev, result.Astats.System.InfName, handler.MultipleSpaceRegex); err != nil {
-		result.PrecomputedData.OutBytes = 0
-		log.Errorf("addkbps %s handle precomputing outbytes '%v'\n", result.ID, err)
-	}
-
-	kbpsInMbps := int64(1000)
-	result.PrecomputedData.MaxKbps = int64(result.Astats.System.InfSpeed) * kbpsInMbps
-
-	for stat, value := range result.Astats.Ats {
-		var err error
-		stats, err = processStat(result.ID, stats, todata, stat, value, result.Time)
-		if err != nil && err != dsdata.ErrNotProcessedStat {
-			log.Infof("precomputing cache %v stat %v value %v error %v", result.ID, stat, value, err)
-			result.PrecomputedData.Errors = append(result.PrecomputedData.Errors, err)
-		}
-	}
-	result.PrecomputedData.DeliveryServiceStats = stats
-	return result
-}
-
-// processStat and its subsidiary functions act as a State Machine, flowing the stat thru states for each "." component of the stat name
-func processStat(server enum.CacheName, stats map[enum.DeliveryServiceName]dsdata.Stat, toData todata.TOData, stat string, value interface{}, timeReceived time.Time) (map[enum.DeliveryServiceName]dsdata.Stat, error) {
-	parts := strings.Split(stat, ".")
-	if len(parts) < 1 {
-		return stats, fmt.Errorf("stat has no initial part")
-	}
-
-	switch parts[0] {
-	case "plugin":
-		return processStatPlugin(server, stats, toData, stat, parts[1:], value, timeReceived)
-	case "proxy":
-		return stats, dsdata.ErrNotProcessedStat
-	case "server":
-		return stats, dsdata.ErrNotProcessedStat
-	default:
-		return stats, fmt.Errorf("stat '%s' has unknown initial part '%s'", stat, parts[0])
-	}
-}
-
-func processStatPlugin(server enum.CacheName, stats map[enum.DeliveryServiceName]dsdata.Stat, toData todata.TOData, stat string, statParts []string, value interface{}, timeReceived time.Time) (map[enum.DeliveryServiceName]dsdata.Stat, error) {
-	if len(statParts) < 1 {
-		return stats, fmt.Errorf("stat has no plugin part")
-	}
-	switch statParts[0] {
-	case "remap_stats":
-		return processStatPluginRemapStats(server, stats, toData, stat, statParts[1:], value, timeReceived)
-	default:
-		return stats, fmt.Errorf("stat has unknown plugin part '%s'", statParts[0])
-	}
-}
-
-func processStatPluginRemapStats(server enum.CacheName, stats map[enum.DeliveryServiceName]dsdata.Stat, toData todata.TOData, stat string, statParts []string, value interface{}, timeReceived time.Time) (map[enum.DeliveryServiceName]dsdata.Stat, error) {
-	if len(statParts) < 2 {
-		return stats, fmt.Errorf("stat has no remap_stats deliveryservice and name parts")
-	}
-
-	fqdn := strings.Join(statParts[:len(statParts)-1], ".")
-
-	ds, ok := toData.DeliveryServiceRegexes.DeliveryService(fqdn)
-	if !ok {
-		return stats, fmt.Errorf("ERROR no delivery service match for fqdn '%v' stat '%v'\n", fqdn, strings.Join(statParts, "."))
-	}
-	if ds == "" {
-		return stats, fmt.Errorf("ERROR EMPTY delivery service fqdn %v stat %v\n", fqdn, strings.Join(statParts, "."))
-	}
-
-	statName := statParts[len(statParts)-1]
-
-	dsStat, ok := stats[ds]
-	if !ok {
-		newStat := dsdata.NewStat()
-		dsStat = *newStat
-	}
-
-	if err := addCacheStat(&dsStat.TotalStats, statName, value); err != nil {
-		return stats, err
-	}
-
-	cachegroup, ok := toData.ServerCachegroups[server]
-	if !ok {
-		return stats, fmt.Errorf("server missing from TOData.ServerCachegroups") // TODO check logs, make sure this isn't normal
-	}
-	dsStat.CacheGroups[cachegroup] = dsStat.TotalStats
-
-	cacheType, ok := toData.ServerTypes[server]
-	if !ok {
-		return stats, fmt.Errorf("server missing from TOData.ServerTypes")
-	}
-	dsStat.Types[cacheType] = dsStat.TotalStats
-
-	dsStat.Caches[server] = dsStat.TotalStats
-
-	dsStat.CachesTimeReceived[server] = timeReceived
-	stats[ds] = dsStat
-	return stats, nil
-}
-
-// addCacheStat adds the given stat to the existing stat. Note this adds, it doesn't overwrite. Numbers are summed, strings are concatenated.
-// TODO make this less duplicate code somehow.
-func addCacheStat(stat *dsdata.StatCacheStats, name string, val interface{}) error {
-	switch name {
-	case "status_2xx":
-		v, ok := val.(float64)
-		if !ok {
-			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
-		}
-		stat.Status2xx.Value += int64(v)
-	case "status_3xx":
-		v, ok := val.(float64)
-		if !ok {
-			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
-		}
-		stat.Status3xx.Value += int64(v)
-	case "status_4xx":
-		v, ok := val.(float64)
-		if !ok {
-			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
-		}
-		stat.Status4xx.Value += int64(v)
-	case "status_5xx":
-		v, ok := val.(float64)
-		if !ok {
-			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
-		}
-		stat.Status5xx.Value += int64(v)
-	case "out_bytes":
-		v, ok := val.(float64)
-		if !ok {
-			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
-		}
-		stat.OutBytes.Value += int64(v)
-	case "is_available":
-		v, ok := val.(bool)
-		if !ok {
-			return fmt.Errorf("stat '%s' value expected bool actual '%v' type %T", name, val, val)
-		}
-		if v {
-			stat.IsAvailable.Value = true
-		}
-	case "in_bytes":
-		v, ok := val.(float64)
-		if !ok {
-			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
-		}
-		stat.InBytes.Value += v
-	case "tps_2xx":
-		v, ok := val.(int64)
-		if !ok {
-			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
-		}
-		stat.Tps2xx.Value += float64(v)
-	case "tps_3xx":
-		v, ok := val.(int64)
-		if !ok {
-			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
-		}
-		stat.Tps3xx.Value += float64(v)
-	case "tps_4xx":
-		v, ok := val.(int64)
-		if !ok {
-			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
-		}
-		stat.Tps4xx.Value += float64(v)
-	case "tps_5xx":
-		v, ok := val.(int64)
-		if !ok {
-			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
-		}
-		stat.Tps5xx.Value += float64(v)
-	case "error_string":
-		v, ok := val.(string)
-		if !ok {
-			return fmt.Errorf("stat '%s' value expected string actual '%v' type %T", name, val, val)
-		}
-		stat.ErrorString.Value += v + ", "
-	case "tps_total":
-		v, ok := val.(float64)
-		if !ok {
-			return fmt.Errorf("stat '%s' value expected int actual '%v' type %T", name, val, val)
-		}
-		stat.TpsTotal.Value += v
-	case "status_unknown":
-		return dsdata.ErrNotProcessedStat
-	default:
-		return fmt.Errorf("unknown stat '%s'", name)
-	}
-	return nil
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/cache/cache_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/cache/cache_test.go b/traffic_monitor/experimental/traffic_monitor/cache/cache_test.go
deleted file mode 100644
index 71e7a78..0000000
--- a/traffic_monitor/experimental/traffic_monitor/cache/cache_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package cache
-
-/*
- * 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"
-	"net/url"
-	"testing"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/peer"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/srvhttp"
-	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/trafficopsdata"
-)
-
-func TestHandlerPrecompute(t *testing.T) {
-	if NewHandler().Precompute() {
-		t.Errorf("expected NewHandler().Precompute() false, actual true")
-	}
-	if !NewPrecomputeHandler(todata.NewThreadsafe(), peer.NewCRStatesPeersThreadsafe()).Precompute() {
-		t.Errorf("expected NewPrecomputeHandler().Precompute() true, actual false")
-	}
-}
-
-type DummyFilterNever struct {
-}
-
-func (f DummyFilterNever) UseStat(name string) bool {
-	return false
-}
-
-func (f DummyFilterNever) UseCache(name enum.CacheName) bool {
-	return false
-}
-
-func (f DummyFilterNever) WithinStatHistoryMax(i int) bool {
-	return false
-}
-
-func TestStatsMarshall(t *testing.T) {
-	hist := randResultHistory()
-	filter := DummyFilterNever{}
-	params := url.Values{}
-	beforeStatsMarshall := time.Now()
-	bytes, err := StatsMarshall(hist, filter, params)
-	afterStatsMarshall := time.Now()
-	if err != nil {
-		t.Fatalf("StatsMarshall return expected nil err, actual err: %v", err)
-	}
-	// if len(bytes) > 0 {
-	// 	t.Errorf("expected empty bytes, actual: %v", string(bytes))
-	// }
-
-	stats := Stats{}
-	if err := json.Unmarshal(bytes, &stats); err != nil {
-		t.Fatalf("unmarshalling expected nil err, actual err: %v", err)
-	}
-
-	if stats.CommonAPIData.QueryParams != "" {
-		t.Errorf(`unmarshalling stats.CommonAPIData.QueryParams expected "", actual %v`, stats.CommonAPIData.QueryParams)
-	}
-
-	statsDate, err := time.Parse(srvhttp.CommonAPIDataDateFormat, stats.CommonAPIData.DateStr)
-	if err != nil {
-		t.Errorf(`stats.CommonAPIData.DateStr expected format %v, actual %v`, srvhttp.CommonAPIDataDateFormat, stats.CommonAPIData.DateStr)
-	}
-	if beforeStatsMarshall.Round(time.Second).After(statsDate) || statsDate.After(afterStatsMarshall.Round(time.Second)) { // round to second, because CommonAPIDataDateFormat is second-precision
-		t.Errorf(`unmarshalling stats.CommonAPIData.DateStr expected between %v and %v, actual %v`, beforeStatsMarshall, afterStatsMarshall, stats.CommonAPIData.DateStr)
-	}
-	if len(stats.Caches) > 0 {
-		t.Errorf(`unmarshalling stats.Caches expected empty, actual %+v`, stats.Caches)
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/cache/data.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/cache/data.go b/traffic_monitor/experimental/traffic_monitor/cache/data.go
deleted file mode 100644
index 81b938a..0000000
--- a/traffic_monitor/experimental/traffic_monitor/cache/data.go
+++ /dev/null
@@ -1,219 +0,0 @@
-package cache
-
-/*
- * 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"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-)
-
-// CacheAvailableStatusReported is the status string returned by caches set to "reported" in Traffic Ops.
-// TODO put somewhere more generic
-const AvailableStatusReported = "REPORTED"
-
-// CacheAvailableStatus is the available status of the given cache. It includes a boolean available/unavailable flag, and a descriptive string.
-type AvailableStatus struct {
-	Available bool
-	Status    string
-	Why       string
-	// UnavailableStat is the stat whose threshold made the cache unavailable. If this is the empty string, the cache is unavailable for a non-threshold reason. This exists so a poller (health, stat) won't mark an unavailable cache as available if the stat whose threshold was reached isn't available on that poller.
-	UnavailableStat string
-	// Poller is the name of the poller which set this available status
-	Poller string
-}
-
-// CacheAvailableStatuses is the available status of each cache.
-type AvailableStatuses map[enum.CacheName]AvailableStatus
-
-// Copy copies this CacheAvailableStatuses. It does not modify, and thus is safe for multiple reader goroutines.
-func (a AvailableStatuses) Copy() AvailableStatuses {
-	b := AvailableStatuses(map[enum.CacheName]AvailableStatus{})
-	for k, v := range a {
-		b[k] = v
-	}
-	return b
-}
-
-// ResultHistory is a map of cache names, to an array of result history from each cache.
-type ResultHistory map[enum.CacheName][]Result
-
-func copyResult(a []Result) []Result {
-	b := make([]Result, len(a), len(a))
-	copy(b, a)
-	return b
-}
-
-// Copy copies returns a deep copy of this ResultHistory
-func (a ResultHistory) Copy() ResultHistory {
-	b := ResultHistory{}
-	for k, v := range a {
-		b[k] = copyResult(v)
-	}
-	return b
-}
-
-// ResultStatHistory is a map[cache][statName]val
-type ResultStatHistory map[enum.CacheName]ResultStatValHistory
-
-type ResultStatValHistory map[string][]ResultStatVal
-
-// ResultStatVal is the value of an individual stat returned from a poll. Time is the time this stat was returned.
-// Span is the number of polls this stat has been the same. For example, if History is set to 100, and the last 50 polls had the same value for this stat (but none of the previous 50 were the same), this stat's map value slice will actually contain 51 entries, and the first entry will have the value, the time of the last poll, and a Span of 50. Assuming the poll time is every 8 seconds, users will then know, looking at the Span, that the value was unchanged for the last 50*8=400 seconds.
-// JSON values are all strings, for the TM1.0 /publish/CacheStats API.
-type ResultStatVal struct {
-	Val  interface{} `json:"value"`
-	Time time.Time   `json:"time"`
-	Span uint64      `json:"span"`
-}
-
-func (t *ResultStatVal) MarshalJSON() ([]byte, error) {
-	v := struct {
-		Val  string `json:"value"`
-		Time int64  `json:"time"`
-		Span uint64 `json:"span"`
-	}{
-		Val:  fmt.Sprintf("%v", t.Val),
-		Time: t.Time.UnixNano() / 1000000, // ms since the epoch
-		Span: t.Span,
-	}
-	return json.Marshal(&v)
-}
-
-func copyResultStatVals(a []ResultStatVal) []ResultStatVal {
-	b := make([]ResultStatVal, len(a), len(a))
-	copy(b, a)
-	return b
-}
-
-func copyResultStatValHistory(a ResultStatValHistory) ResultStatValHistory {
-	b := ResultStatValHistory{}
-	for k, v := range a {
-		b[k] = copyResultStatVals(v) // TODO determine if necessary
-	}
-	return b
-}
-
-func (a ResultStatHistory) Copy() ResultStatHistory {
-	b := ResultStatHistory{}
-	for k, v := range a {
-		b[k] = copyResultStatValHistory(v)
-	}
-	return b
-}
-
-func pruneStats(history []ResultStatVal, limit uint64) []ResultStatVal {
-	if uint64(len(history)) > limit {
-		history = history[:limit-1]
-	}
-	return history
-}
-
-func (a ResultStatHistory) Add(r Result, limit uint64) {
-	for statName, statVal := range r.Astats.Ats {
-		statHistory := a[r.ID][statName]
-		// If the new stat value is the same as the last, update the time and increment the span. Span is the number of polls the latest value has been the same, and hence the length of time it's been the same is span*pollInterval.
-		if len(statHistory) > 0 && statHistory[0].Val == statVal {
-			statHistory[0].Time = r.Time
-			statHistory[0].Span++
-		} else {
-			resultVal := ResultStatVal{
-				Val:  statVal,
-				Time: r.Time,
-				Span: 1,
-			}
-			statHistory = pruneStats(append([]ResultStatVal{resultVal}, statHistory...), limit)
-		}
-		if _, ok := a[r.ID]; !ok {
-			a[r.ID] = ResultStatValHistory{}
-		}
-		a[r.ID][statName] = statHistory // TODO determine if necessary for the first conditional
-	}
-}
-
-// TODO determine if anything ever needs more than the latest, and if not, change ResultInfo to not be a slice.
-type ResultInfoHistory map[enum.CacheName][]ResultInfo
-
-// ResultInfo contains all the non-stat result info. This includes the cache ID, any errors, the time of the poll, the request time duration, Astats System (Vitals), Poll ID, and Availability.
-type ResultInfo struct {
-	ID          enum.CacheName
-	Error       error
-	Time        time.Time
-	RequestTime time.Duration
-	Vitals      Vitals
-	System      AstatsSystem
-	PollID      uint64
-	Available   bool
-}
-
-func ToInfo(r Result) ResultInfo {
-	return ResultInfo{
-		ID:          r.ID,
-		Error:       r.Error,
-		Time:        r.Time,
-		RequestTime: r.RequestTime,
-		Vitals:      r.Vitals,
-		PollID:      r.PollID,
-		Available:   r.Available,
-		System:      r.Astats.System,
-	}
-}
-
-func copyResultInfos(a []ResultInfo) []ResultInfo {
-	b := make([]ResultInfo, len(a), len(a))
-	copy(b, a)
-	return b
-}
-
-func (a ResultInfoHistory) Copy() ResultInfoHistory {
-	b := ResultInfoHistory{}
-	for k, v := range a {
-		b[k] = copyResultInfos(v) // TODO determine if copy is necessary
-	}
-	return b
-}
-
-func pruneInfos(history []ResultInfo, limit uint64) []ResultInfo {
-	if uint64(len(history)) > limit {
-		history = history[:limit-1]
-	}
-	return history
-}
-
-func (a ResultInfoHistory) Add(r Result, limit uint64) {
-	a[r.ID] = pruneInfos(append([]ResultInfo{ToInfo(r)}, a[r.ID]...), limit)
-}
-
-// Kbpses is the kbps values of each cache.
-type Kbpses map[enum.CacheName]int64
-
-func (a Kbpses) Copy() Kbpses {
-	b := Kbpses{}
-	for k, v := range a {
-		b[k] = v
-	}
-	return b
-}
-
-func (a Kbpses) AddMax(r Result) {
-	a[r.ID] = r.PrecomputedData.MaxKbps
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/cache/data_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/cache/data_test.go b/traffic_monitor/experimental/traffic_monitor/cache/data_test.go
deleted file mode 100644
index 72ee851..0000000
--- a/traffic_monitor/experimental/traffic_monitor/cache/data_test.go
+++ /dev/null
@@ -1,256 +0,0 @@
-package cache
-
-/*
- * 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 (
-	"errors"
-	"fmt"
-	dsdata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/deliveryservicedata"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-	"math/rand"
-	"reflect"
-	"testing"
-	"time"
-)
-
-func randBool() bool {
-	return rand.Int()%2 == 0
-}
-
-func randStr() string {
-	chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_"
-	num := 100
-	s := ""
-	for i := 0; i < num; i++ {
-		s += string(chars[rand.Intn(len(chars))])
-	}
-	return s
-}
-
-func randAvailableStatuses() AvailableStatuses {
-	a := AvailableStatuses{}
-	num := 100
-	for i := 0; i < num; i++ {
-		a[enum.CacheName(randStr())] = AvailableStatus{Available: randBool(), Status: randStr()}
-	}
-	return a
-}
-
-func TestAvailableStatusesCopy(t *testing.T) {
-	num := 100
-	for i := 0; i < num; i++ {
-		a := randAvailableStatuses()
-		b := a.Copy()
-
-		if !reflect.DeepEqual(a, b) {
-			t.Errorf("expected a and b DeepEqual, actual copied map not equal", a, b)
-		}
-
-		// verify a and b don't point to the same map
-		a[enum.CacheName(randStr())] = AvailableStatus{Available: randBool(), Status: randStr()}
-		if reflect.DeepEqual(a, b) {
-			t.Errorf("expected a != b, actual a and b point to the same map", a)
-		}
-	}
-}
-
-func randStrIfaceMap() map[string]interface{} {
-	m := map[string]interface{}{}
-	num := 5
-	for i := 0; i < num; i++ {
-		m[randStr()] = randStr()
-	}
-	return m
-}
-
-func randAstats() Astats {
-	return Astats{
-		Ats:    randStrIfaceMap(),
-		System: randAstatsSystem(),
-	}
-}
-
-func randAstatsSystem() AstatsSystem {
-	return AstatsSystem{
-		InfName:           randStr(),
-		InfSpeed:          rand.Int(),
-		ProcNetDev:        randStr(),
-		ProcLoadavg:       randStr(),
-		ConfigLoadRequest: rand.Int(),
-		LastReloadRequest: rand.Int(),
-		ConfigReloads:     rand.Int(),
-		LastReload:        rand.Int(),
-		AstatsLoad:        rand.Int(),
-	}
-}
-
-func randVitals() Vitals {
-	return Vitals{
-		LoadAvg:    rand.Float64(),
-		BytesOut:   rand.Int63(),
-		BytesIn:    rand.Int63(),
-		KbpsOut:    rand.Int63(),
-		MaxKbpsOut: rand.Int63(),
-	}
-}
-
-func randStatMeta() dsdata.StatMeta {
-	return dsdata.StatMeta{Time: rand.Int63()}
-}
-
-func randStatCacheStats() dsdata.StatCacheStats {
-	return dsdata.StatCacheStats{
-		OutBytes:    dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
-		IsAvailable: dsdata.StatBool{Value: randBool(), StatMeta: randStatMeta()},
-		Status5xx:   dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
-		Status4xx:   dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
-		Status3xx:   dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
-		Status2xx:   dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
-		InBytes:     dsdata.StatFloat{Value: rand.Float64(), StatMeta: randStatMeta()},
-		Kbps:        dsdata.StatFloat{Value: rand.Float64(), StatMeta: randStatMeta()},
-		Tps5xx:      dsdata.StatFloat{Value: rand.Float64(), StatMeta: randStatMeta()},
-		Tps4xx:      dsdata.StatFloat{Value: rand.Float64(), StatMeta: randStatMeta()},
-		Tps3xx:      dsdata.StatFloat{Value: rand.Float64(), StatMeta: randStatMeta()},
-		Tps2xx:      dsdata.StatFloat{Value: rand.Float64(), StatMeta: randStatMeta()},
-		ErrorString: dsdata.StatString{Value: randStr(), StatMeta: randStatMeta()},
-		TpsTotal:    dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
-	}
-}
-
-func randStatCommon() dsdata.StatCommon {
-	cachesReporting := map[enum.CacheName]bool{}
-	num := 5
-	for i := 0; i < num; i++ {
-		cachesReporting[enum.CacheName(randStr())] = randBool()
-	}
-	return dsdata.StatCommon{
-		CachesConfiguredNum: dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
-		CachesReporting:     cachesReporting,
-		ErrorStr:            dsdata.StatString{Value: randStr(), StatMeta: randStatMeta()},
-		StatusStr:           dsdata.StatString{Value: randStr(), StatMeta: randStatMeta()},
-		IsHealthy:           dsdata.StatBool{Value: randBool(), StatMeta: randStatMeta()},
-		IsAvailable:         dsdata.StatBool{Value: randBool(), StatMeta: randStatMeta()},
-		CachesAvailableNum:  dsdata.StatInt{Value: rand.Int63(), StatMeta: randStatMeta()},
-	}
-}
-
-func randDsStat() dsdata.Stat {
-	num := 5
-	cacheGroups := map[enum.CacheGroupName]dsdata.StatCacheStats{}
-	types := map[enum.CacheType]dsdata.StatCacheStats{}
-	caches := map[enum.CacheName]dsdata.StatCacheStats{}
-	cachesTime := map[enum.CacheName]time.Time{}
-	for i := 0; i < num; i++ {
-		cacheGroups[enum.CacheGroupName(randStr())] = randStatCacheStats()
-		types[enum.CacheType(randStr())] = randStatCacheStats()
-		cachesTime[enum.CacheName(randStr())] = time.Now()
-	}
-
-	return dsdata.Stat{
-		CommonStats:        randStatCommon(),
-		CacheGroups:        cacheGroups,
-		Types:              types,
-		Caches:             caches,
-		CachesTimeReceived: cachesTime,
-		TotalStats:         randStatCacheStats(),
-	}
-}
-
-func randDsStats() map[enum.DeliveryServiceName]dsdata.Stat {
-	num := 5
-	a := map[enum.DeliveryServiceName]dsdata.Stat{}
-	for i := 0; i < num; i++ {
-		a[enum.DeliveryServiceName(randStr())] = randDsStat()
-	}
-	return a
-}
-func randErrs() []error {
-	if randBool() {
-		return []error{}
-	}
-	num := 5
-	errs := []error{}
-	for i := 0; i < num; i++ {
-		errs = append(errs, errors.New(randStr()))
-	}
-	return errs
-}
-
-func randPrecomputedData() PrecomputedData {
-	return PrecomputedData{
-		DeliveryServiceStats: randDsStats(),
-		OutBytes:             rand.Int63(),
-		MaxKbps:              rand.Int63(),
-		Errors:               randErrs(),
-		Reporting:            randBool(),
-	}
-}
-
-func randResult() Result {
-	return Result{
-		ID:              enum.CacheName(randStr()),
-		Error:           fmt.Errorf(randStr()),
-		Astats:          randAstats(),
-		Time:            time.Now(),
-		RequestTime:     time.Millisecond * time.Duration(rand.Int()),
-		Vitals:          randVitals(),
-		PollID:          uint64(rand.Int63()),
-		PollFinished:    make(chan uint64),
-		PrecomputedData: randPrecomputedData(),
-		Available:       randBool(),
-	}
-
-}
-
-func randResultSlice() []Result {
-	a := []Result{}
-	num := 5
-	for i := 0; i < num; i++ {
-		a = append(a, randResult())
-	}
-	return a
-}
-
-func randResultHistory() ResultHistory {
-	a := ResultHistory{}
-	num := 5
-	for i := 0; i < num; i++ {
-		a[enum.CacheName(randStr())] = randResultSlice()
-	}
-	return a
-}
-
-func TestResultHistoryCopy(t *testing.T) {
-	num := 5
-	for i := 0; i < num; i++ {
-		a := randResultHistory()
-		b := a.Copy()
-
-		if !reflect.DeepEqual(a, b) {
-			t.Errorf("expected a and b DeepEqual, actual copied map not equal", a, b)
-		}
-
-		// verify a and b don't point to the same map
-		a[enum.CacheName(randStr())] = randResultSlice()
-		if reflect.DeepEqual(a, b) {
-			t.Errorf("expected a != b, actual a and b point to the same map", a)
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/config/config.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/config/config.go b/traffic_monitor/experimental/traffic_monitor/config/config.go
deleted file mode 100644
index 1941ec6..0000000
--- a/traffic_monitor/experimental/traffic_monitor/config/config.go
+++ /dev/null
@@ -1,186 +0,0 @@
-package config
-
-/*
- * 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"
-	"io/ioutil"
-	"time"
-)
-
-// LogLocation is a location to log to. This may be stdout, stderr, null (/dev/null), or a valid file path.
-type LogLocation string
-
-const (
-	// LogLocationStdout indicates the stdout IO stream
-	LogLocationStdout = "stdout"
-	// LogLocationStderr indicates the stderr IO stream
-	LogLocationStderr = "stderr"
-	// LogLocationNull indicates the null IO stream (/dev/null)
-	LogLocationNull = "null"
-	//StaticFileDir is the directory that contains static html and js files.
-	StaticFileDir = "/opt/traffic_monitor/static/"
-)
-
-// Config is the configuration for the application. It includes myriad data, such as polling intervals and log locations.
-type Config struct {
-	CacheHealthPollingInterval   time.Duration `json:"-"`
-	CacheStatPollingInterval     time.Duration `json:"-"`
-	MonitorConfigPollingInterval time.Duration `json:"-"`
-	HTTPTimeout                  time.Duration `json:"-"`
-	PeerPollingInterval          time.Duration `json:"-"`
-	PeerOptimistic               bool          `json:"peer_optimistic"`
-	MaxEvents                    uint64        `json:"max_events"`
-	MaxStatHistory               uint64        `json:"max_stat_history"`
-	MaxHealthHistory             uint64        `json:"max_health_history"`
-	HealthFlushInterval          time.Duration `json:"-"`
-	StatFlushInterval            time.Duration `json:"-"`
-	LogLocationError             string        `json:"log_location_error"`
-	LogLocationWarning           string        `json:"log_location_warning"`
-	LogLocationInfo              string        `json:"log_location_info"`
-	LogLocationDebug             string        `json:"log_location_debug"`
-	LogLocationEvent             string        `json:"log_location_event"`
-	ServeReadTimeout             time.Duration `json:"-"`
-	ServeWriteTimeout            time.Duration `json:"-"`
-	HealthToStatRatio            uint64        `json:"health_to_stat_ratio"`
-	HTTPPollNoSleep              bool          `json:"http_poll_no_sleep"`
-	StaticFileDir                string        `json:"static_file_dir"`
-}
-
-// DefaultConfig is the default configuration for the application, if no configuration file is given, or if a given config setting doesn't exist in the config file.
-var DefaultConfig = Config{
-	CacheHealthPollingInterval:   6 * time.Second,
-	CacheStatPollingInterval:     6 * time.Second,
-	MonitorConfigPollingInterval: 5 * time.Second,
-	HTTPTimeout:                  2 * time.Second,
-	PeerPollingInterval:          5 * time.Second,
-	PeerOptimistic:               true,
-	MaxEvents:                    200,
-	MaxStatHistory:               5,
-	MaxHealthHistory:             5,
-	HealthFlushInterval:          200 * time.Millisecond,
-	StatFlushInterval:            200 * time.Millisecond,
-	LogLocationError:             LogLocationStderr,
-	LogLocationWarning:           LogLocationStdout,
-	LogLocationInfo:              LogLocationNull,
-	LogLocationDebug:             LogLocationNull,
-	LogLocationEvent:             LogLocationStdout,
-	ServeReadTimeout:             10 * time.Second,
-	ServeWriteTimeout:            10 * time.Second,
-	HealthToStatRatio:            4,
-	HTTPPollNoSleep:              false,
-	StaticFileDir:                StaticFileDir,
-}
-
-// MarshalJSON marshals custom millisecond durations. Aliasing inspired by http://choly.ca/post/go-json-marshalling/
-func (c *Config) MarshalJSON() ([]byte, error) {
-	type Alias Config
-	return json.Marshal(&struct {
-		CacheHealthPollingIntervalMs   uint64 `json:"cache_health_polling_interval_ms"`
-		CacheStatPollingIntervalMs     uint64 `json:"cache_stat_polling_interval_ms"`
-		MonitorConfigPollingIntervalMs uint64 `json:"monitor_config_polling_interval_ms"`
-		HTTPTimeoutMS                  uint64 `json:"http_timeout_ms"`
-		PeerPollingIntervalMs          uint64 `json:"peer_polling_interval_ms"`
-		PeerOptimistic                 bool   `json:"peer_optimistic"`
-		HealthFlushIntervalMs          uint64 `json:"health_flush_interval_ms"`
-		StatFlushIntervalMs            uint64 `json:"stat_flush_interval_ms"`
-		ServeReadTimeoutMs             uint64 `json:"serve_read_timeout_ms"`
-		ServeWriteTimeoutMs            uint64 `json:"serve_write_timeout_ms"`
-		*Alias
-	}{
-		CacheHealthPollingIntervalMs:   uint64(c.CacheHealthPollingInterval / time.Millisecond),
-		CacheStatPollingIntervalMs:     uint64(c.CacheStatPollingInterval / time.Millisecond),
-		MonitorConfigPollingIntervalMs: uint64(c.MonitorConfigPollingInterval / time.Millisecond),
-		HTTPTimeoutMS:                  uint64(c.HTTPTimeout / time.Millisecond),
-		PeerPollingIntervalMs:          uint64(c.PeerPollingInterval / time.Millisecond),
-		PeerOptimistic:                 bool(true),
-		HealthFlushIntervalMs:          uint64(c.HealthFlushInterval / time.Millisecond),
-		StatFlushIntervalMs:            uint64(c.StatFlushInterval / time.Millisecond),
-		Alias:                          (*Alias)(c),
-	})
-}
-
-// UnmarshalJSON populates this config object from given JSON bytes.
-func (c *Config) UnmarshalJSON(data []byte) error {
-	type Alias Config
-	aux := &struct {
-		CacheHealthPollingIntervalMs   *uint64 `json:"cache_health_polling_interval_ms"`
-		CacheStatPollingIntervalMs     *uint64 `json:"cache_stat_polling_interval_ms"`
-		MonitorConfigPollingIntervalMs *uint64 `json:"monitor_config_polling_interval_ms"`
-		HTTPTimeoutMS                  *uint64 `json:"http_timeout_ms"`
-		PeerPollingIntervalMs          *uint64 `json:"peer_polling_interval_ms"`
-		PeerOptimistic                 *bool   `json:"peer_optimistic"`
-		HealthFlushIntervalMs          *uint64 `json:"health_flush_interval_ms"`
-		StatFlushIntervalMs            *uint64 `json:"stat_flush_interval_ms"`
-		ServeReadTimeoutMs             *uint64 `json:"serve_read_timeout_ms"`
-		ServeWriteTimeoutMs            *uint64 `json:"serve_write_timeout_ms"`
-		*Alias
-	}{
-		Alias: (*Alias)(c),
-	}
-	if err := json.Unmarshal(data, &aux); err != nil {
-		return err
-	}
-
-	if aux.CacheHealthPollingIntervalMs != nil {
-		c.CacheHealthPollingInterval = time.Duration(*aux.CacheHealthPollingIntervalMs) * time.Millisecond
-	}
-	if aux.CacheStatPollingIntervalMs != nil {
-		c.CacheStatPollingInterval = time.Duration(*aux.CacheStatPollingIntervalMs) * time.Millisecond
-	}
-	if aux.MonitorConfigPollingIntervalMs != nil {
-		c.MonitorConfigPollingInterval = time.Duration(*aux.MonitorConfigPollingIntervalMs) * time.Millisecond
-	}
-	if aux.HTTPTimeoutMS != nil {
-		c.HTTPTimeout = time.Duration(*aux.HTTPTimeoutMS) * time.Millisecond
-	}
-	if aux.PeerPollingIntervalMs != nil {
-		c.PeerPollingInterval = time.Duration(*aux.PeerPollingIntervalMs) * time.Millisecond
-	}
-	if aux.HealthFlushIntervalMs != nil {
-		c.HealthFlushInterval = time.Duration(*aux.HealthFlushIntervalMs) * time.Millisecond
-	}
-	if aux.StatFlushIntervalMs != nil {
-		c.StatFlushInterval = time.Duration(*aux.StatFlushIntervalMs) * time.Millisecond
-	}
-	if aux.ServeReadTimeoutMs != nil {
-		c.ServeReadTimeout = time.Duration(*aux.ServeReadTimeoutMs) * time.Millisecond
-	}
-	if aux.ServeWriteTimeoutMs != nil {
-		c.ServeWriteTimeout = time.Duration(*aux.ServeWriteTimeoutMs) * time.Millisecond
-	}
-	if aux.PeerOptimistic != nil {
-		c.PeerOptimistic = *aux.PeerOptimistic
-	}
-	return nil
-}
-
-// Load loads the given config file. If an empty string is passed, the default config is returned.
-func Load(fileName string) (Config, error) {
-	cfg := DefaultConfig
-	if fileName == "" {
-		return cfg, nil
-	}
-	configBytes, err := ioutil.ReadFile(fileName)
-	if err == nil {
-		err = json.Unmarshal(configBytes, &cfg)
-	}
-	return cfg, err
-}


[05/19] incubator-trafficcontrol git commit: Move TM2 to trafficcontrol/traffic_monitor_golang

Posted by ne...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/threadsafe/polledcaches.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/threadsafe/polledcaches.go b/traffic_monitor_golang/traffic_monitor/threadsafe/polledcaches.go
new file mode 100644
index 0000000..1cd4c18
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/threadsafe/polledcaches.go
@@ -0,0 +1,147 @@
+package threadsafe
+
+/*
+ * 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 (
+	"sync"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/cache"
+	dsdata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/deliveryservicedata"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+)
+
+// UnpolledCaches is a structure containing a map of caches which have yet to be polled, which is threadsafe for multiple readers and one writer.
+// This could be made lock-free, if the performance was necessary
+type UnpolledCaches struct {
+	unpolledCaches *map[enum.CacheName]struct{}
+	allCaches      *map[enum.CacheName]struct{}
+	initialized    *bool
+	m              *sync.RWMutex
+}
+
+// NewUnpolledCaches returns a new UnpolledCaches object.
+func NewUnpolledCaches() UnpolledCaches {
+	b := false
+	return UnpolledCaches{
+		m:              &sync.RWMutex{},
+		unpolledCaches: &map[enum.CacheName]struct{}{},
+		allCaches:      &map[enum.CacheName]struct{}{},
+		initialized:    &b,
+	}
+}
+
+// UnpolledCaches returns a map of caches not yet polled. Callers MUST NOT modify. If mutation is necessary, copy the map
+func (t *UnpolledCaches) UnpolledCaches() map[enum.CacheName]struct{} {
+	t.m.RLock()
+	defer t.m.RUnlock()
+	return *t.unpolledCaches
+}
+
+// setUnpolledCaches sets the internal unpolled caches map. This is only safe for one thread of execution. This MUST NOT be called from multiple threads.
+func (t *UnpolledCaches) setUnpolledCaches(v map[enum.CacheName]struct{}) {
+	t.m.Lock()
+	*t.initialized = true
+	*t.unpolledCaches = v
+	t.m.Unlock()
+}
+
+// SetNewCaches takes a list of new caches, which may overlap with the existing caches, diffs them, removes any `unpolledCaches` which aren't in the new list, and sets the list of `polledCaches` (which is only used by this func) to the `newCaches`. This is threadsafe with one writer, along with `setUnpolledCaches`.
+func (t *UnpolledCaches) SetNewCaches(newCaches map[enum.CacheName]struct{}) {
+	unpolledCaches := copyCaches(t.UnpolledCaches())
+	allCaches := copyCaches(*t.allCaches) // not necessary to lock `allCaches`, as the single-writer is the only thing that accesses it.
+	for cache := range unpolledCaches {
+		if _, ok := newCaches[cache]; !ok {
+			delete(unpolledCaches, cache)
+		}
+	}
+	for cache := range allCaches {
+		if _, ok := newCaches[cache]; !ok {
+			delete(allCaches, cache)
+		}
+	}
+	for cache := range newCaches {
+		if _, ok := allCaches[cache]; !ok {
+			unpolledCaches[cache] = struct{}{}
+			allCaches[cache] = struct{}{}
+		}
+	}
+	*t.allCaches = allCaches
+	t.setUnpolledCaches(unpolledCaches)
+}
+
+// Any returns whether there are any caches marked as not polled. Also returns true if SetNewCaches() has never been called (assuming there exist caches, if this hasn't been initialized, we couldn't have polled any of them).
+func (t *UnpolledCaches) Any() bool {
+	t.m.Lock()
+	defer t.m.Unlock()
+	return !(*t.initialized) || len(*t.unpolledCaches) > 0
+}
+
+// copyCaches performs a deep copy of the given map.
+func copyCaches(a map[enum.CacheName]struct{}) map[enum.CacheName]struct{} {
+	b := map[enum.CacheName]struct{}{}
+	for k := range a {
+		b[k] = struct{}{}
+	}
+	return b
+}
+
+// SetPolled sets cache which have been polled. This is used to determine when the app has fully started up, and we can start serving. Serving Traffic Router with caches as 'down' which simply haven't been polled yet would be bad. Therefore, a cache is set as 'polled' if it has received different bandwidths from two different ATS ticks, OR if the cache is marked as down (and thus we won't get a bandwidth).
+// This is threadsafe for one writer, along with `Set`.
+// This is fast if there are no unpolled caches. Moreover, its speed is a function of the number of unpolled caches, not the number of caches total.
+func (t *UnpolledCaches) SetPolled(results []cache.Result, lastStats dsdata.LastStats) {
+	unpolledCaches := copyCaches(t.UnpolledCaches())
+	numUnpolledCaches := len(unpolledCaches)
+	if numUnpolledCaches == 0 {
+		return
+	}
+	for cache := range unpolledCaches {
+	innerLoop:
+		for _, result := range results {
+			if result.ID != cache {
+				continue
+			}
+
+			if !result.Available || result.Error != nil {
+				log.Debugf("polled %v\n", cache)
+				delete(unpolledCaches, cache)
+				break innerLoop
+			}
+		}
+		lastStat, ok := lastStats.Caches[cache]
+		if !ok {
+			continue
+		}
+		if lastStat.Bytes.PerSec != 0 {
+			log.Debugf("polled %v\n", cache)
+			delete(unpolledCaches, cache)
+		}
+	}
+
+	if len(unpolledCaches) == numUnpolledCaches {
+		return
+	}
+	t.setUnpolledCaches(unpolledCaches)
+	if len(unpolledCaches) != 0 {
+		log.Infof("remaining unpolled %v\n", unpolledCaches)
+	} else {
+		log.Infof("all caches polled, ready to serve!\n")
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/threadsafe/resulthistory.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/threadsafe/resulthistory.go b/traffic_monitor_golang/traffic_monitor/threadsafe/resulthistory.go
new file mode 100644
index 0000000..320aabf
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/threadsafe/resulthistory.go
@@ -0,0 +1,54 @@
+package threadsafe
+
+/*
+ * 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 (
+	"sync"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/cache"
+)
+
+// ResultHistory provides safe access for multiple goroutines readers and a single writer to a stored ResultHistory object.
+// This could be made lock-free, if the performance was necessary
+// TODO add separate locks for Caches and Deliveryservice maps?
+type ResultHistory struct {
+	resultHistory *cache.ResultHistory
+	m             *sync.RWMutex
+}
+
+// NewResultHistory returns a new ResultHistory safe for multiple readers and a single writer.
+func NewResultHistory() ResultHistory {
+	h := cache.ResultHistory{}
+	return ResultHistory{m: &sync.RWMutex{}, resultHistory: &h}
+}
+
+// Get returns the ResultHistory. Callers MUST NOT modify. If mutation is necessary, call ResultHistory.Copy()
+func (h *ResultHistory) Get() cache.ResultHistory {
+	h.m.RLock()
+	defer h.m.RUnlock()
+	return *h.resultHistory
+}
+
+// Set sets the internal ResultHistory. This is only safe for one thread of execution. This MUST NOT be called from multiple threads.
+func (h *ResultHistory) Set(v cache.ResultHistory) {
+	h.m.Lock()
+	*h.resultHistory = v
+	h.m.Unlock()
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/threadsafe/resultstathistory.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/threadsafe/resultstathistory.go b/traffic_monitor_golang/traffic_monitor/threadsafe/resultstathistory.go
new file mode 100644
index 0000000..6ea76b0
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/threadsafe/resultstathistory.go
@@ -0,0 +1,81 @@
+// TODO rename
+package threadsafe
+
+/*
+ * 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 (
+	"sync"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/cache"
+)
+
+// ResultStatHistory provides safe access for multiple goroutines readers and a single writer to a stored HistoryHistory object.
+// This could be made lock-free, if the performance was necessary
+type ResultStatHistory struct {
+	history *cache.ResultStatHistory
+	m       *sync.RWMutex
+}
+
+// NewResultStatHistory returns a new ResultStatHistory safe for multiple readers and a single writer.
+func NewResultStatHistory() ResultStatHistory {
+	h := cache.ResultStatHistory{}
+	return ResultStatHistory{m: &sync.RWMutex{}, history: &h}
+}
+
+// Get returns the ResultStatHistory. Callers MUST NOT modify. If mutation is necessary, call ResultStatHistory.Copy()
+func (h *ResultStatHistory) Get() cache.ResultStatHistory {
+	h.m.RLock()
+	defer h.m.RUnlock()
+	return *h.history
+}
+
+// Set sets the internal ResultStatHistory. This is only safe for one thread of execution. This MUST NOT be called from multiple threads.
+func (h *ResultStatHistory) Set(v cache.ResultStatHistory) {
+	h.m.Lock()
+	*h.history = v
+	h.m.Unlock()
+}
+
+// ResultStatHistory provides safe access for multiple goroutines readers and a single writer to a stored HistoryHistory object.
+// This could be made lock-free, if the performance was necessary
+type ResultInfoHistory struct {
+	history *cache.ResultInfoHistory
+	m       *sync.RWMutex
+}
+
+// NewResultInfoHistory returns a new ResultInfoHistory safe for multiple readers and a single writer.
+func NewResultInfoHistory() ResultInfoHistory {
+	h := cache.ResultInfoHistory{}
+	return ResultInfoHistory{m: &sync.RWMutex{}, history: &h}
+}
+
+// Get returns the ResultInfoHistory. Callers MUST NOT modify. If mutation is necessary, call ResultInfoHistory.Copy()
+func (h *ResultInfoHistory) Get() cache.ResultInfoHistory {
+	h.m.RLock()
+	defer h.m.RUnlock()
+	return *h.history
+}
+
+// Set sets the internal ResultInfoHistory. This is only safe for one thread of execution. This MUST NOT be called from multiple threads.
+func (h *ResultInfoHistory) Set(v cache.ResultInfoHistory) {
+	h.m.Lock()
+	*h.history = v
+	h.m.Unlock()
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/threadsafe/uint.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/threadsafe/uint.go b/traffic_monitor_golang/traffic_monitor/threadsafe/uint.go
new file mode 100644
index 0000000..fcaa3a4
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/threadsafe/uint.go
@@ -0,0 +1,51 @@
+package threadsafe
+
+/*
+ * 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 (
+	"sync/atomic"
+)
+
+// Uint provides safe access for multiple goroutines readers and a single writer to a stored uint.
+type Uint struct {
+	val *uint64
+}
+
+// NewUint returns a new single-writer-multiple-reader threadsafe uint
+func NewUint() Uint {
+	v := uint64(0)
+	return Uint{val: &v}
+}
+
+// Get gets the internal uint. This is safe for multiple readers
+func (u *Uint) Get() uint64 {
+	return atomic.LoadUint64(u.val)
+}
+
+// Set sets the internal uint. This MUST NOT be called by multiple goroutines.
+func (u *Uint) Set(v uint64) {
+	atomic.StoreUint64(u.val, v)
+}
+
+// Inc increments the internal uint64.
+// TODO make sure everything using this uses the value it returns, not a separate Get
+func (u *Uint) Inc() uint64 {
+	return atomic.AddUint64(u.val, 1)
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/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
new file mode 100644
index 0000000..d77b628
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/traffic_monitor.go
@@ -0,0 +1,157 @@
+package main
+
+/*
+ * 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 (
+	"bytes"
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"math"
+	"os"
+	"os/exec"
+	"runtime"
+	"time"
+
+	_ "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/instrumentation"
+	"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/manager"
+	_ "github.com/davecheney/gmx"
+)
+
+// GitRevision is the git revision of the app. The app SHOULD always be built with this set via the `-X` flag.
+var GitRevision = "No Git Revision Specified. Please build with '-X main.GitRevision=${git rev-parse HEAD}'"
+
+// 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
+	}
+
+	return d, nil
+}
+
+func getLogWriter(location string) (io.Writer, error) {
+	switch location {
+	case config.LogLocationStdout:
+		return os.Stdout, nil
+	case config.LogLocationStderr:
+		return os.Stderr, nil
+	case config.LogLocationNull:
+		return ioutil.Discard, nil
+	default:
+		return os.OpenFile(location, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
+	}
+}
+func getLogWriters(eventLoc, errLoc, warnLoc, infoLoc, debugLoc string) (io.Writer, io.Writer, io.Writer, io.Writer, io.Writer, error) {
+	eventW, err := getLogWriter(eventLoc)
+	if err != nil {
+		return nil, nil, nil, nil, nil, fmt.Errorf("getting log event writer %v: %v", eventLoc, err)
+	}
+	errW, err := getLogWriter(errLoc)
+	if err != nil {
+		return nil, nil, nil, nil, nil, fmt.Errorf("getting log error writer %v: %v", errLoc, err)
+	}
+	warnW, err := getLogWriter(warnLoc)
+	if err != nil {
+		return nil, nil, nil, nil, nil, fmt.Errorf("getting log warning writer %v: %v", warnLoc, err)
+	}
+	infoW, err := getLogWriter(infoLoc)
+	if err != nil {
+		return nil, nil, nil, nil, nil, fmt.Errorf("getting log info writer %v: %v", infoLoc, err)
+	}
+	debugW, err := getLogWriter(debugLoc)
+	if err != nil {
+		return nil, nil, nil, nil, nil, fmt.Errorf("getting log debug writer %v: %v", debugLoc, err)
+	}
+	return eventW, errW, warnW, infoW, debugW, nil
+}
+
+func main() {
+	runtime.GOMAXPROCS(runtime.NumCPU())
+
+	staticData, err := getStaticAppData()
+	if err != nil {
+		fmt.Printf("Error starting service: failed to get static app data: %v\n", err)
+		os.Exit(1)
+	}
+
+	opsConfigFile := flag.String("opsCfg", "", "The traffic ops config file")
+	configFileName := flag.String("config", "", "The Traffic Monitor config file path")
+	flag.Parse()
+
+	if *opsConfigFile == "" {
+		fmt.Println("Error starting service: The --opsCfg argument is required")
+		os.Exit(1)
+	}
+
+	// TODO add hot reloading (like opsConfigFile)?
+	cfg, err := config.Load(*configFileName)
+	if err != nil {
+		fmt.Printf("Error starting service: failed to load config: %v\n", err)
+		os.Exit(1)
+	}
+
+	eventW, errW, warnW, infoW, debugW, err := getLogWriters(cfg.LogLocationEvent, cfg.LogLocationError, cfg.LogLocationWarning, cfg.LogLocationInfo, cfg.LogLocationDebug)
+	if err != nil {
+		fmt.Printf("Error starting service: failed to create log writers: %v\n", err)
+		os.Exit(1)
+	}
+	log.Init(eventW, errW, warnW, infoW, debugW)
+
+	log.Infof("Starting with config %+v\n", cfg)
+
+	manager.Start(*opsConfigFile, cfg, staticData)
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/trafficopsdata/trafficopsdata.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/trafficopsdata/trafficopsdata.go b/traffic_monitor_golang/traffic_monitor/trafficopsdata/trafficopsdata.go
new file mode 100644
index 0000000..69c7af1
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/trafficopsdata/trafficopsdata.go
@@ -0,0 +1,290 @@
+package trafficopsdata
+
+/*
+ * 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"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+	towrap "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopswrapper"
+	"regexp"
+	"strings"
+	"sync"
+)
+
+// Regexes maps Delivery Service Regular Expressions to delivery services.
+// For performance, we categorize Regular Expressions into 3 categories:
+// 1. Direct string matches, with no regular expression matching characters
+// 2. .*\.foo\..* expressions, where foo is a direct string match with no regular expression matching characters
+// 3. Everything else
+// This allows us to do a cheap match on 1 and 2, and only regex match the uncommon case.
+// TODO performance tests, whether Go compiled *Regexp is relevantly slower than `strings.Contains` for direct and .foo. matches
+type Regexes struct {
+	DirectMatches                      map[string]enum.DeliveryServiceName
+	DotStartSlashDotFooSlashDotDotStar map[string]enum.DeliveryServiceName
+	RegexMatch                         map[*regexp.Regexp]enum.DeliveryServiceName
+}
+
+// DeliveryService returns the delivery service which matches the given fqdn, or false.
+func (d Regexes) DeliveryService(fqdn string) (enum.DeliveryServiceName, bool) {
+	if ds, ok := d.DirectMatches[fqdn]; ok {
+		return ds, true
+	}
+	for matchStr, ds := range d.DotStartSlashDotFooSlashDotDotStar {
+		if strings.Contains(fqdn, "."+matchStr+".") {
+			return ds, true
+		}
+	}
+	for regex, ds := range d.RegexMatch {
+		if regex.MatchString(fqdn) {
+			return ds, true
+		}
+	}
+	return "", false
+}
+
+// NewRegexes constructs a new Regexes object, initializing internal pointer members.
+func NewRegexes() Regexes {
+	return Regexes{DirectMatches: map[string]enum.DeliveryServiceName{}, DotStartSlashDotFooSlashDotDotStar: map[string]enum.DeliveryServiceName{}, RegexMatch: map[*regexp.Regexp]enum.DeliveryServiceName{}}
+}
+
+// TOData holds CDN data fetched from Traffic Ops.
+type TOData struct {
+	DeliveryServiceServers map[enum.DeliveryServiceName][]enum.CacheName
+	ServerDeliveryServices map[enum.CacheName][]enum.DeliveryServiceName
+	ServerTypes            map[enum.CacheName]enum.CacheType
+	DeliveryServiceTypes   map[enum.DeliveryServiceName]enum.DSType
+	DeliveryServiceRegexes Regexes
+	ServerCachegroups      map[enum.CacheName]enum.CacheGroupName
+}
+
+// New returns a new empty TOData object, initializing pointer members.
+func New() *TOData {
+	return &TOData{
+		DeliveryServiceServers: map[enum.DeliveryServiceName][]enum.CacheName{},
+		ServerDeliveryServices: map[enum.CacheName][]enum.DeliveryServiceName{},
+		ServerTypes:            map[enum.CacheName]enum.CacheType{},
+		DeliveryServiceTypes:   map[enum.DeliveryServiceName]enum.DSType{},
+		DeliveryServiceRegexes: NewRegexes(),
+		ServerCachegroups:      map[enum.CacheName]enum.CacheGroupName{},
+	}
+}
+
+// TODataThreadsafe provides safe access for multiple goroutine writers and one goroutine reader, to the encapsulated TOData object.
+// This could be made lock-free, if the performance was necessary
+type TODataThreadsafe struct {
+	toData *TOData
+	m      *sync.RWMutex
+}
+
+// NewThreadsafe returns a new TOData object, wrapped to be safe for multiple goroutine readers and a single writer.
+func NewThreadsafe() TODataThreadsafe {
+	return TODataThreadsafe{m: &sync.RWMutex{}, toData: New()}
+}
+
+// Get returns the current TOData. Callers MUST NOT modify returned data. Mutation IS NOT threadsafe
+// If callers need to modify, a new GetMutable() should be added which copies.
+func (d TODataThreadsafe) Get() TOData {
+	d.m.RLock()
+	defer d.m.RUnlock()
+	return *d.toData
+}
+
+func (d TODataThreadsafe) set(newTOData TOData) {
+	d.m.Lock()
+	*d.toData = newTOData
+	d.m.Unlock()
+}
+
+// CRConfig is the CrConfig data needed by TOData. Note this is not all data in the CRConfig.
+// TODO change strings to type?
+type CRConfig struct {
+	ContentServers map[enum.CacheName]struct {
+		DeliveryServices map[enum.DeliveryServiceName][]string `json:"deliveryServices"`
+		CacheGroup       string                                `json:"cacheGroup"`
+		Type             string                                `json:"type"`
+	} `json:"contentServers"`
+	DeliveryServices map[enum.DeliveryServiceName]struct {
+		Matchsets []struct {
+			Protocol  string `json:"protocol"`
+			MatchList []struct {
+				Regex string `json:"regex"`
+			} `json:"matchlist"`
+		} `json:"matchsets"`
+	} `json:"deliveryServices"`
+}
+
+// Fetch gets the CRConfig from Traffic Ops, creates the TOData maps, and atomically sets the TOData.
+// TODO since the session is threadsafe, each TOData get func below could be put in a goroutine, if performance mattered
+func (d TODataThreadsafe) Fetch(to towrap.ITrafficOpsSession, cdn string) error {
+	newTOData := TOData{}
+
+	crConfigBytes, err := to.CRConfigRaw(cdn)
+	if err != nil {
+		return fmt.Errorf("Error getting CRconfig from Traffic Ops: %v", err)
+	}
+	var crConfig CRConfig
+	err = json.Unmarshal(crConfigBytes, &crConfig)
+	if err != nil {
+		return fmt.Errorf("Error unmarshalling CRconfig: %v", err)
+	}
+
+	newTOData.DeliveryServiceServers, newTOData.ServerDeliveryServices, err = getDeliveryServiceServers(crConfig)
+	if err != nil {
+		return err
+	}
+
+	newTOData.DeliveryServiceTypes, err = getDeliveryServiceTypes(crConfig)
+	if err != nil {
+		return fmt.Errorf("Error getting delivery service types from Traffic Ops: %v\n", err)
+	}
+
+	newTOData.DeliveryServiceRegexes, err = getDeliveryServiceRegexes(crConfig)
+	if err != nil {
+		return fmt.Errorf("Error getting delivery service regexes from Traffic Ops: %v\n", err)
+	}
+
+	newTOData.ServerCachegroups, err = getServerCachegroups(crConfig)
+	if err != nil {
+		return fmt.Errorf("Error getting server cachegroups from Traffic Ops: %v\n", err)
+	}
+
+	newTOData.ServerTypes, err = getServerTypes(crConfig)
+	if err != nil {
+		return fmt.Errorf("Error getting server types from Traffic Ops: %v\n", err)
+	}
+
+	d.set(newTOData)
+	return nil
+}
+
+// getDeliveryServiceServers gets the servers on each delivery services, for the given CDN, from Traffic Ops.
+func getDeliveryServiceServers(crc CRConfig) (map[enum.DeliveryServiceName][]enum.CacheName, map[enum.CacheName][]enum.DeliveryServiceName, error) {
+	dsServers := map[enum.DeliveryServiceName][]enum.CacheName{}
+	serverDses := map[enum.CacheName][]enum.DeliveryServiceName{}
+
+	for serverName, serverData := range crc.ContentServers {
+		for deliveryServiceName := range serverData.DeliveryServices {
+			dsServers[deliveryServiceName] = append(dsServers[deliveryServiceName], serverName)
+			serverDses[serverName] = append(serverDses[serverName], deliveryServiceName)
+		}
+	}
+	return dsServers, serverDses, nil
+}
+
+// getDeliveryServiceRegexes gets the regexes of each delivery service, for the given CDN, from Traffic Ops.
+// Returns a map[deliveryService][]regex.
+func getDeliveryServiceRegexes(crc CRConfig) (Regexes, error) {
+	dsRegexes := map[enum.DeliveryServiceName][]string{}
+
+	for dsName, dsData := range crc.DeliveryServices {
+		if len(dsData.Matchsets) < 1 {
+			return Regexes{}, fmt.Errorf("CRConfig missing regex for '%s'", dsName)
+		}
+		for _, matchset := range dsData.Matchsets {
+			if len(matchset.MatchList) < 1 {
+				return Regexes{}, fmt.Errorf("CRConfig missing Regex for '%s'", dsName)
+			}
+			dsRegexes[dsName] = append(dsRegexes[dsName], matchset.MatchList[0].Regex)
+		}
+	}
+
+	return createRegexes(dsRegexes)
+}
+
+// TODO precompute, move to TOData; call when we get new delivery services, instead of every time we create new stats
+func createRegexes(dsToRegex map[enum.DeliveryServiceName][]string) (Regexes, error) {
+	dsRegexes := Regexes{
+		DirectMatches:                      map[string]enum.DeliveryServiceName{},
+		DotStartSlashDotFooSlashDotDotStar: map[string]enum.DeliveryServiceName{},
+		RegexMatch:                         map[*regexp.Regexp]enum.DeliveryServiceName{},
+	}
+
+	for ds, regexStrs := range dsToRegex {
+		for _, regexStr := range regexStrs {
+			prefix := `.*\.`
+			suffix := `\..*`
+			if strings.HasPrefix(regexStr, prefix) && strings.HasSuffix(regexStr, suffix) {
+				matchStr := regexStr[len(prefix) : len(regexStr)-len(suffix)]
+				if otherDs, ok := dsRegexes.DotStartSlashDotFooSlashDotDotStar[matchStr]; ok {
+					return dsRegexes, fmt.Errorf("duplicate regex %s (%s) in %s and %s", regexStr, matchStr, ds, otherDs)
+				}
+				dsRegexes.DotStartSlashDotFooSlashDotDotStar[matchStr] = ds
+				continue
+			}
+			if !strings.ContainsAny(regexStr, `[]^\:{}()|?+*,=%@<>!'`) {
+				if otherDs, ok := dsRegexes.DirectMatches[regexStr]; ok {
+					return dsRegexes, fmt.Errorf("duplicate Regex %s in %s and %s", regexStr, ds, otherDs)
+				}
+				dsRegexes.DirectMatches[regexStr] = ds
+				continue
+			}
+			// TODO warn? regex matches are unusual
+			r, err := regexp.Compile(regexStr)
+			if err != nil {
+				return dsRegexes, fmt.Errorf("regex %s failed to compile: %v", regexStr, err)
+			}
+			dsRegexes.RegexMatch[r] = ds
+		}
+	}
+	return dsRegexes, nil
+}
+
+// getServerCachegroups gets the cachegroup of each ATS Edge+Mid Cache server, for the given CDN, from Traffic Ops.
+// Returns a map[server]cachegroup.
+func getServerCachegroups(crc CRConfig) (map[enum.CacheName]enum.CacheGroupName, error) {
+	serverCachegroups := map[enum.CacheName]enum.CacheGroupName{}
+
+	for server, serverData := range crc.ContentServers {
+		serverCachegroups[server] = enum.CacheGroupName(serverData.CacheGroup)
+	}
+	return serverCachegroups, nil
+}
+
+// getServerTypes gets the cache type of each ATS Edge+Mid Cache server, for the given CDN, from Traffic Ops.
+func getServerTypes(crc CRConfig) (map[enum.CacheName]enum.CacheType, error) {
+	serverTypes := map[enum.CacheName]enum.CacheType{}
+
+	for server, serverData := range crc.ContentServers {
+		t := enum.CacheTypeFromString(serverData.Type)
+		if t == enum.CacheTypeInvalid {
+			return nil, fmt.Errorf("getServerTypes CRConfig unknown type for '%s': '%s'", server, serverData.Type)
+		}
+		serverTypes[server] = t
+	}
+	return serverTypes, nil
+}
+
+func getDeliveryServiceTypes(crc CRConfig) (map[enum.DeliveryServiceName]enum.DSType, error) {
+	dsTypes := map[enum.DeliveryServiceName]enum.DSType{}
+
+	for dsName, dsData := range crc.DeliveryServices {
+		if len(dsData.Matchsets) < 1 {
+			return nil, fmt.Errorf("CRConfig missing protocol for '%s'", dsName)
+		}
+		dsTypeStr := dsData.Matchsets[0].Protocol
+		dsType := enum.DSTypeFromString(dsTypeStr)
+		if dsType == enum.DSTypeInvalid {
+			return nil, fmt.Errorf("CRConfig unknowng protocol for '%s': '%s'", dsName, dsTypeStr)
+		}
+		dsTypes[dsName] = dsType
+	}
+	return dsTypes, nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/trafficopswrapper/trafficopswrapper.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/trafficopswrapper/trafficopswrapper.go b/traffic_monitor_golang/traffic_monitor/trafficopswrapper/trafficopswrapper.go
new file mode 100644
index 0000000..3c48543
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/trafficopswrapper/trafficopswrapper.go
@@ -0,0 +1,128 @@
+package trafficopswrapper
+
+/*
+ * 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"
+	"sync"
+
+	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
+)
+
+// ITrafficOpsSession provides an interface to the Traffic Ops client, so it may be wrapped or mocked.
+type ITrafficOpsSession interface {
+	CRConfigRaw(cdn string) ([]byte, error)
+	TrafficMonitorConfigMap(cdn string) (*to.TrafficMonitorConfigMap, error)
+	Set(session *to.Session)
+	URL() (string, error)
+	User() (string, error)
+	Servers() ([]to.Server, error)
+	Parameters(profileName string) ([]to.Parameter, error)
+	DeliveryServices() ([]to.DeliveryService, error)
+}
+
+var ErrNilSession = fmt.Errorf("nil session")
+
+func (s TrafficOpsSessionThreadsafe) URL() (string, error) {
+	s.m.Lock()
+	defer s.m.Unlock()
+	if s.session == nil || *s.session == nil {
+		return "", ErrNilSession
+	}
+	url := (*s.session).URL
+	return url, nil
+}
+
+func (s TrafficOpsSessionThreadsafe) User() (string, error) {
+	s.m.Lock()
+	defer s.m.Unlock()
+	if s.session == nil || *s.session == nil {
+		return "", ErrNilSession
+	}
+	user := (*s.session).UserName
+	return user, nil
+}
+
+// TrafficOpsSessionThreadsafe provides access to the Traffic Ops client safe for multiple goroutines. This fulfills the ITrafficOpsSession interface.
+type TrafficOpsSessionThreadsafe struct {
+	session **to.Session // pointer-to-pointer, because we're given a pointer from the Traffic Ops package, and we don't want to copy it.
+	m       *sync.Mutex
+}
+
+// NewTrafficOpsSessionThreadsafe returns a new threadsafe TrafficOpsSessionThreadsafe wrapping the given `Session`.
+func NewTrafficOpsSessionThreadsafe(s *to.Session) TrafficOpsSessionThreadsafe {
+	return TrafficOpsSessionThreadsafe{&s, &sync.Mutex{}}
+}
+
+// CRConfigRaw returns the CRConfig from the Traffic Ops. This is safe for multiple goroutines.
+func (s TrafficOpsSessionThreadsafe) CRConfigRaw(cdn string) ([]byte, error) {
+	s.m.Lock()
+	defer s.m.Unlock()
+	if s.session == nil || *s.session == nil {
+		return nil, ErrNilSession
+	}
+	b, _, e := (*s.session).GetCRConfig(cdn)
+	return b, e
+}
+
+// TrafficMonitorConfigMap returns the Traffic Monitor config map from the Traffic Ops. This is safe for multiple goroutines.
+func (s TrafficOpsSessionThreadsafe) TrafficMonitorConfigMap(cdn string) (*to.TrafficMonitorConfigMap, error) {
+	s.m.Lock()
+	defer s.m.Unlock()
+	if s.session == nil || *s.session == nil {
+		return nil, ErrNilSession
+	}
+	d, e := (*s.session).TrafficMonitorConfigMap(cdn)
+	return d, e
+}
+
+// Set sets the internal Traffic Ops session. This is safe for multiple goroutines, being aware they will race.
+func (s TrafficOpsSessionThreadsafe) Set(session *to.Session) {
+	s.m.Lock()
+	defer s.m.Unlock()
+	*s.session = session
+}
+
+func (s TrafficOpsSessionThreadsafe) Servers() ([]to.Server, error) {
+	s.m.Lock()
+	defer s.m.Unlock()
+	if s.session == nil || *s.session == nil {
+		return nil, ErrNilSession
+	}
+	return (*s.session).Servers()
+}
+
+func (s TrafficOpsSessionThreadsafe) Parameters(profileName string) ([]to.Parameter, error) {
+	s.m.Lock()
+	defer s.m.Unlock()
+	if s.session == nil || *s.session == nil {
+		return nil, ErrNilSession
+	}
+	return (*s.session).Parameters(profileName)
+}
+
+func (s TrafficOpsSessionThreadsafe) DeliveryServices() ([]to.DeliveryService, error) {
+	s.m.Lock()
+	defer s.m.Unlock()
+	if s.session == nil || *s.session == nil {
+		return nil, ErrNilSession
+	}
+	return (*s.session).DeliveryServices()
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/version.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/version.go b/traffic_monitor_golang/traffic_monitor/version.go
new file mode 100644
index 0000000..c1ecc0c
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/version.go
@@ -0,0 +1,23 @@
+package main
+
+/*
+ * 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.
+ */
+
+// Version is the current version of the app, in string form.
+var Version = "2.0.1"

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/github.com/davecheney/gmx/.gitignore
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/github.com/davecheney/gmx/.gitignore b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/.gitignore
new file mode 100644
index 0000000..19c3660
--- /dev/null
+++ b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/.gitignore
@@ -0,0 +1 @@
+gmxc/gmxc

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/github.com/davecheney/gmx/LICENCE
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/github.com/davecheney/gmx/LICENCE b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/LICENCE
new file mode 100644
index 0000000..d7f4f29
--- /dev/null
+++ b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/LICENCE
@@ -0,0 +1,8 @@
+Copyright (c) 2012, David Cheney
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/github.com/davecheney/gmx/README.md
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/github.com/davecheney/gmx/README.md b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/README.md
new file mode 100644
index 0000000..b66d10c
--- /dev/null
+++ b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/README.md
@@ -0,0 +1,52 @@
+# Go management extensions
+
+## Installation
+  
+	go get github.com/davecheney/gmx
+
+## Getting started
+
+Instrumenting your application with gmx is as simple as importing the `gmx` package in your `main` package via the side effect operator.
+
+	package main
+
+	import _ "github.com/davecheney/gmx"
+
+By default gmx opens a unix socket in `/tmp`, the name of the socket is
+
+	/tmp/.gmx.$PID.0
+
+## Protocol version 0
+
+The current protocol version is 0, which is a simple JSON based protocol. You can communicate with the gmx socket using a tool like socat.
+
+	% socat UNIX-CONNECT:/tmp/.gmx.$(pgrep godoc).0 stdin
+	["runtime.version", "runtime.numcpu"]
+	{"runtime.numcpu":4,"runtime.version":"weekly.2012-01-27 11688+"}
+     
+The request is a json array of strings representing keys that you wish to query. The result is a json map, the keys of that map are keys that matched the keys in your request. The value of the entry will be the result of the published function, encoded in json. If there is no matching key registered, no entry will appear in the result map.
+
+For convenience a client is included in the gmxc sub directory. Please consult the `README` in that directory for more details.
+
+## Registering gmx keys
+
+New keys can be registered using the `Publish` function
+
+	gmx.Publish(key string, f func() interface{})
+
+`f` can be any function that returns a json encodable result. `f` is executed whenever its key is invoked, responsibility for ensuring the function is thread safe rests with the author of `f`.
+
+## Runtime instrumentation
+
+By default gmx instruments selected values from the  `runtime` and `os` packages, refer to the `runtime.go` and `os.go` source for more details.
+
+## Changelog
+
+6/Feb/2012 
+
++	gmx now honors the value of os.TempDir() when opening the unix socket
++	gmxc now accepts regexps for key names
+
+5/Feb/2012 
+
++	Initial release

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/github.com/davecheney/gmx/example/helloworld/README.md
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/github.com/davecheney/gmx/example/helloworld/README.md b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/example/helloworld/README.md
new file mode 100644
index 0000000..6cc9401
--- /dev/null
+++ b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/example/helloworld/README.md
@@ -0,0 +1,19 @@
+# helloworld example
+
+Install the gmxc client
+
+	go get github.com/davecheney/gmx/gmxc
+
+Install this example 
+
+	go get github.com/davecheney/gmx/example/helloworld
+
+Run the example in the background
+
+	$GOBIN/helloworld &
+
+Query it via gmxc
+
+	$GOBIN/gmxc -p $(pgrep helloworld) hello
+	hello: world
+

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/github.com/davecheney/gmx/example/helloworld/main.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/github.com/davecheney/gmx/example/helloworld/main.go b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/example/helloworld/main.go
new file mode 100644
index 0000000..f6d2765
--- /dev/null
+++ b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/example/helloworld/main.go
@@ -0,0 +1,14 @@
+package main
+
+import "github.com/davecheney/gmx"
+
+func init() {
+	gmx.Publish("hello", func() interface{} {
+		return "world"
+	})
+}
+
+func main() {
+	// sleep forever
+	select {}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/github.com/davecheney/gmx/gmx.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/github.com/davecheney/gmx/gmx.go b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/gmx.go
new file mode 100644
index 0000000..5b446fc
--- /dev/null
+++ b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/gmx.go
@@ -0,0 +1,129 @@
+package gmx
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"log"
+	"net"
+	"os"
+	"path/filepath"
+	"sync"
+)
+
+const GMX_VERSION = 0
+
+var (
+	r = &registry{
+		entries: make(map[string]func() interface{}),
+	}
+)
+
+func init() {
+	s, err := localSocket()
+	if err != nil {
+		log.Printf("gmx: unable to open local socket: %v", err)
+		return
+	}
+
+	// register the registries keys for discovery
+	Publish("keys", func() interface{} {
+		return r.keys()
+	})
+	go serve(s, r)
+}
+
+func localSocket() (net.Listener, error) {
+	return net.ListenUnix("unix", localSocketAddr())
+}
+
+func localSocketAddr() *net.UnixAddr {
+	return &net.UnixAddr{
+		filepath.Join(os.TempDir(), fmt.Sprintf(".gmx.%d.%d", os.Getpid(), GMX_VERSION)),
+		"unix",
+	}
+}
+
+// Publish registers the function f with the supplied key.
+func Publish(key string, f func() interface{}) {
+	r.register(key, f)
+}
+
+func serve(l net.Listener, r *registry) {
+	// if listener is a unix socket, try to delete it on shutdown
+	if l, ok := l.(*net.UnixListener); ok {
+		if a, ok := l.Addr().(*net.UnixAddr); ok {
+			defer os.Remove(a.Name)
+		}
+	}
+	defer l.Close()
+	for {
+		c, err := l.Accept()
+		if err != nil {
+			return
+		}
+		go handle(c, r)
+	}
+}
+
+func handle(nc net.Conn, reg *registry) {
+	// conn makes it easier to send and receive json
+	type conn struct {
+		net.Conn
+		*json.Encoder
+		*json.Decoder
+	}
+	c := conn{
+		nc,
+		json.NewEncoder(nc),
+		json.NewDecoder(nc),
+	}
+	defer c.Close()
+	for {
+		var keys []string
+		if err := c.Decode(&keys); err != nil {
+			if err != io.EOF {
+				log.Printf("gmx: client %v sent invalid json request: %v", c.RemoteAddr(), err)
+			}
+			return
+		}
+		var result = make(map[string]interface{})
+		for _, key := range keys {
+			if f, ok := reg.value(key); ok {
+				// invoke the function for key and store the result
+				result[key] = f()
+			}
+		}
+		if err := c.Encode(result); err != nil {
+			log.Printf("gmx: could not send response to client %v: %v", c.RemoteAddr(), err)
+			return
+		}
+	}
+}
+
+type registry struct {
+	sync.Mutex // protects entries from concurrent mutation
+	entries    map[string]func() interface{}
+}
+
+func (r *registry) register(key string, f func() interface{}) {
+	r.Lock()
+	defer r.Unlock()
+	r.entries[key] = f
+}
+
+func (r *registry) value(key string) (func() interface{}, bool) {
+	r.Lock()
+	defer r.Unlock()
+	f, ok := r.entries[key]
+	return f, ok
+}
+
+func (r *registry) keys() (k []string) {
+	r.Lock()
+	defer r.Unlock()
+	for e := range r.entries {
+		k = append(k, e)
+	}
+	return
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/github.com/davecheney/gmx/gmxc/README.md
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/github.com/davecheney/gmx/gmxc/README.md b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/gmxc/README.md
new file mode 100644
index 0000000..79ead26
--- /dev/null
+++ b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/gmxc/README.md
@@ -0,0 +1,27 @@
+# gmx client
+
+`gmxc` is a simple command line client for interacting with gmx enabled processes.
+
+## Usage
+
+### Listing processes
+
+The default invocation of `gmxc` will list the accessible gmx enabled processes currently running
+
+	% ./gmxc 
+	.gmx.16207.0    ["./godoc" "-v" "-http=:8080"]
+
+### Retrieving gmx values
+
+	./gmxc -p 16207 runtime.numcpu
+	runtime.numcpu: 4
+
+	./gmxc -p 14968 'runtime.(numcpu|version)'
+	runtime.version: weekly.2012-01-27 11662
+	runtime.numcpu: 2
+
+### Listing all gmx values
+
+	% ./gmxc -p 16207 keys
+	keys: [keys runtime.memstats runtime.gomaxprocs runtime.version os.args runtime.numcpu runtime.cgocalls]
+

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/github.com/davecheney/gmx/gmxc/main.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/github.com/davecheney/gmx/gmxc/main.go b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/gmxc/main.go
new file mode 100644
index 0000000..071c55e
--- /dev/null
+++ b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/gmxc/main.go
@@ -0,0 +1,129 @@
+package main
+
+import (
+	"encoding/json"
+	"flag"
+	"fmt"
+	"log"
+	"net"
+	"os"
+	"path/filepath"
+	"regexp"
+	"time"
+)
+
+var (
+	delay    = flag.Duration("d", 0, "delay between updates")
+	duration = flag.Duration("D", 0, "duration to output continually")
+
+	pid = flag.Int("p", 0, "process to inspect")
+
+	socketregex = regexp.MustCompile(`\.gmx\.[0-9]+\.0`)
+)
+
+type conn struct {
+	net.Conn
+	*json.Decoder
+	*json.Encoder
+}
+
+func dial(addr string) (*conn, error) {
+	c, err := net.Dial("unix", addr)
+	return &conn{
+		c,
+		json.NewDecoder(c),
+		json.NewEncoder(c),
+	}, err
+}
+
+func listGmxProcesses() {
+	dir, err := os.Open(os.TempDir())
+	if err != nil {
+		log.Fatalf("unable to open %s: %v", os.TempDir(), err)
+	}
+	pids, err := dir.Readdirnames(0)
+	if err != nil {
+		log.Fatalf("unable to read pids: %v", err)
+	}
+	for _, pid := range pids {
+		if socketregex.MatchString(pid) {
+			c, err := dial(filepath.Join(os.TempDir(), pid))
+			if err != nil {
+				continue
+			}
+			defer c.Close()
+			c.Encode([]string{"os.args"})
+			var result = make(map[string]interface{})
+			if err := c.Decode(&result); err != nil {
+				log.Printf("unable to decode response from %s: %v", pid, err)
+				continue
+			}
+			if args, ok := result["os.args"]; ok {
+				fmt.Printf("%s\t%v\n", pid, args)
+			}
+		}
+	}
+}
+
+// fetchKeys returns all the registered keys from the process.
+func fetchKeys(c *conn) []string {
+	// retrieve list of registered keys
+	if err := c.Encode([]string{"keys"}); err != nil {
+		log.Fatalf("unable to send keys request to process: %v", err)
+	}
+	var result = make(map[string][]string)
+	if err := c.Decode(&result); err != nil {
+		log.Fatalf("unable to decode keys response: %v", err)
+	}
+	keys, ok := result["keys"]
+	if !ok {
+		log.Fatalf("gmx server did not return a keys list")
+	}
+	return keys
+}
+
+func main() {
+	flag.Parse()
+	if *pid == 0 {
+		listGmxProcesses()
+		return
+	}
+	c, err := dial(filepath.Join(os.TempDir(), fmt.Sprintf(".gmx.%d.0", *pid)))
+	if err != nil {
+		log.Fatalf("unable to connect to process %d: %v", *pid, err)
+	}
+	defer c.Close()
+
+	// match flag.Args() as regexps
+	registeredKeys := fetchKeys(c)
+	var keys []string
+	for _, a := range flag.Args() {
+		r, err := regexp.Compile(a)
+		if err != nil {
+			log.Fatal("unable to compile regex %v: %v", a, err)
+		}
+		for _, k := range registeredKeys {
+			if r.MatchString(k) {
+				keys = append(keys, k)
+			}
+		}
+	}
+
+	deadline := time.Now().Add(*duration)
+	for {
+		if err := c.Encode(keys); err != nil {
+			log.Fatalf("unable to send request to process: %v", err)
+		}
+		var result = make(map[string]interface{})
+		if err := c.Decode(&result); err != nil {
+			log.Fatalf("unable to decode response: %v", err)
+		}
+		for k, v := range result {
+			fmt.Printf("%s: %v\n", k, v)
+		}
+		if time.Now().After(deadline) {
+			return
+		}
+		time.Sleep(*delay)
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/github.com/davecheney/gmx/instrument.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/github.com/davecheney/gmx/instrument.go b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/instrument.go
new file mode 100644
index 0000000..68c4ca5
--- /dev/null
+++ b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/instrument.go
@@ -0,0 +1,41 @@
+package gmx
+
+import (
+	"sync/atomic"
+)
+
+type Counter struct {
+	value uint64
+}
+
+func (c *Counter) Inc() {
+	atomic.AddUint64(&c.value, 1)
+}
+
+func NewCounter(name string) *Counter {
+	c := new(Counter)
+	Publish(name, func() interface{} {
+		return c.value
+	})
+	return c
+}
+
+type Gauge struct {
+	value int64
+}
+
+func (g *Gauge) Inc() {
+	atomic.AddInt64(&g.value, 1)
+}
+
+func (g *Gauge) Dec() {
+	atomic.AddInt64(&g.value, -1)
+}
+
+func NewGauge(name string) *Gauge {
+	g := new(Gauge)
+	Publish(name, func() interface{} {
+		return g.value
+	})
+	return g
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/github.com/davecheney/gmx/os.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/github.com/davecheney/gmx/os.go b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/os.go
new file mode 100644
index 0000000..92dd372
--- /dev/null
+++ b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/os.go
@@ -0,0 +1,15 @@
+package gmx
+
+// pkg/os instrumentation
+
+import (
+	"os"
+)
+
+func init() {
+	Publish("os.args", osArgs)
+}
+
+func osArgs() interface{} {
+	return os.Args
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/github.com/davecheney/gmx/runtime.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/github.com/davecheney/gmx/runtime.go b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/runtime.go
new file mode 100644
index 0000000..ada86fd
--- /dev/null
+++ b/traffic_monitor_golang/vendor/github.com/davecheney/gmx/runtime.go
@@ -0,0 +1,42 @@
+package gmx
+
+// pkg/runtime instrumentation
+
+import "runtime"
+
+var memstats runtime.MemStats
+
+func init() {
+	Publish("runtime.gomaxprocs", runtimeGOMAXPROCS)
+	Publish("runtime.numcgocall", runtimeNumCgoCall)
+	Publish("runtime.numcpu", runtimeNumCPU)
+	Publish("runtime.numgoroutine", runtimeNumGoroutine)
+	Publish("runtime.version", runtimeVersion)
+
+	Publish("runtime.memstats", runtimeMemStats)
+}
+
+func runtimeGOMAXPROCS() interface{} {
+	return runtime.GOMAXPROCS(0)
+}
+
+func runtimeNumCgoCall() interface{} {
+	return runtime.NumCgoCall()
+}
+
+func runtimeNumCPU() interface{} {
+	return runtime.NumCPU()
+}
+
+func runtimeNumGoroutine() interface{} {
+	return runtime.NumGoroutine()
+}
+
+func runtimeVersion() interface{} {
+	return runtime.Version()
+}
+
+func runtimeMemStats() interface{} {
+	runtime.ReadMemStats(&memstats)
+	return memstats
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/LICENSE
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/LICENSE b/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/LICENSE
new file mode 100644
index 0000000..efcb241
--- /dev/null
+++ b/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/LICENSE
@@ -0,0 +1,10 @@
+Copyright (c) 2014, Eric Urban
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/README.md
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/README.md b/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/README.md
new file mode 100644
index 0000000..bf19193
--- /dev/null
+++ b/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/README.md
@@ -0,0 +1,18 @@
+stoppableListener
+=================
+
+An example of a stoppable TCP listener in Go. This library wraps an existing TCP connection object. A goroutine calling `Accept()`
+is interrupted with `StoppedError` whenever the listener is stopped by a call to `Stop()`. Usage is demonstrated below, and in `example/example.go`.
+
+
+```
+	originalListener, err := net.Listen("tcp", ":8080")
+	if err != nil {
+		panic(err)
+	}
+
+	sl, err := stoppableListener.New(originalListener)
+	if err != nil {
+		panic(err)
+	}
+```

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/example/example.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/example/example.go b/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/example/example.go
new file mode 100644
index 0000000..3c8b323
--- /dev/null
+++ b/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/example/example.go
@@ -0,0 +1,52 @@
+package main
+
+import (
+	"fmt"
+	"github.com/hydrogen18/stoppableListener"
+	"net"
+	"net/http"
+	"os"
+	"os/signal"
+	"sync"
+	"syscall"
+)
+
+func helloHttp(rw http.ResponseWriter, req *http.Request) {
+	rw.WriteHeader(http.StatusOK)
+	fmt.Fprintf(rw, "Hello HTTP!\n")
+}
+
+func main() {
+	originalListener, err := net.Listen("tcp", ":8080")
+	if err != nil {
+		panic(err)
+	}
+
+	sl, err := stoppableListener.New(originalListener)
+	if err != nil {
+		panic(err)
+	}
+
+	http.HandleFunc("/", helloHttp)
+	server := http.Server{}
+
+	stop := make(chan os.Signal)
+	signal.Notify(stop, syscall.SIGINT)
+	var wg sync.WaitGroup
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		server.Serve(sl)
+	}()
+
+	fmt.Printf("Serving HTTP\n")
+	select {
+	case signal := <-stop:
+		fmt.Printf("Got signal:%v\n", signal)
+	}
+	fmt.Printf("Stopping listener\n")
+	sl.Stop()
+	fmt.Printf("Waiting on server\n")
+	wg.Wait()
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/listener.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/listener.go b/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/listener.go
new file mode 100644
index 0000000..69a9f33
--- /dev/null
+++ b/traffic_monitor_golang/vendor/github.com/hydrogen18/stoppableListener/listener.go
@@ -0,0 +1,62 @@
+package stoppableListener
+
+import (
+	"errors"
+	"net"
+	"time"
+)
+
+type StoppableListener struct {
+	*net.TCPListener          //Wrapped listener
+	stop             chan int //Channel used only to indicate listener should shutdown
+}
+
+func New(l net.Listener) (*StoppableListener, error) {
+	tcpL, ok := l.(*net.TCPListener)
+
+	if !ok {
+		return nil, errors.New("Cannot wrap listener")
+	}
+
+	retval := &StoppableListener{}
+	retval.TCPListener = tcpL
+	retval.stop = make(chan int)
+
+	return retval, nil
+}
+
+var StoppedError = errors.New("Listener stopped")
+
+func (sl *StoppableListener) Accept() (net.Conn, error) {
+
+	for {
+		//Wait up to one second for a new connection
+		sl.SetDeadline(time.Now().Add(time.Second))
+
+		newConn, err := sl.TCPListener.Accept()
+
+		//Check for the channel being closed
+		select {
+		case <-sl.stop:
+			return nil, StoppedError
+		default:
+			//If the channel is still open, continue as normal
+		}
+
+		if err != nil {
+			netErr, ok := err.(net.Error)
+
+			//If this is a timeout, then continue to wait for
+			//new connections
+			if ok && netErr.Timeout() && netErr.Temporary() {
+				continue
+			}
+		}
+
+		return newConn, err
+	}
+}
+
+func (sl *StoppableListener) Stop() {
+	close(sl.stop)
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/.gitignore
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/.gitignore b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/.gitignore
new file mode 100644
index 0000000..4cd0cba
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/.gitignore
@@ -0,0 +1,6 @@
+# Setup a Global .gitignore for OS and editor generated files:
+# https://help.github.com/articles/ignoring-files
+# git config --global core.excludesfile ~/.gitignore_global
+
+.vagrant
+*.sublime-project

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/.travis.yml
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/.travis.yml b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/.travis.yml
new file mode 100644
index 0000000..00fd5dd
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/.travis.yml
@@ -0,0 +1,29 @@
+sudo: false
+language: go
+
+go:
+  - 1.5.4
+  - 1.6.1
+  - tip
+
+matrix:
+  allow_failures:
+    - go: tip
+
+before_script:
+  - go get -u github.com/golang/lint/golint
+
+script:
+  - go test -v --race ./...
+
+after_script:
+  - test -z "$(gofmt -s -l -w . | tee /dev/stderr)"
+  - test -z "$(golint ./...     | tee /dev/stderr)"
+  - go vet ./...
+
+os:
+  - linux
+  - osx
+
+notifications:
+  email: false

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/AUTHORS
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/AUTHORS b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/AUTHORS
new file mode 100644
index 0000000..6438bb3
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/AUTHORS
@@ -0,0 +1,43 @@
+# Names should be added to this file as
+#	Name or Organization <email address>
+# The email address is not required for organizations.
+
+# You can update this list using the following command:
+#
+#   $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
+
+# Please keep the list sorted.
+
+Adrien Bustany <ad...@bustany.org>
+Amit Krishnan <am...@oracle.com>
+Bj�rn Erik Pedersen <bj...@gmail.com>
+Caleb Spare <ce...@gmail.com>
+Case Nelson <ca...@teammating.com>
+Chris Howey <ch...@howey.me> <ho...@gmail.com>
+Christoffer Buchholz <ch...@gmail.com>
+Daniel Wagner-Hall <da...@gmail.com>
+Dave Cheney <da...@cheney.net>
+Evan Phoenix <ev...@fallingsnow.net>
+Francisco Souza <f...@souza.cc>
+Hari haran <ha...@gmail.com>
+John C Barstow
+Kelvin Fo <vm...@gmail.com>
+Ken-ichirou MATSUZAWA <ch...@h4.dion.ne.jp>
+Matt Layher <md...@gmail.com>
+Nathan Youngman <gi...@nathany.com>
+Paul Hammond <pa...@paulhammond.org>
+Pawel Knap <pa...@gmail.com>
+Pieter Droogendijk <pi...@binky.org.uk>
+Pursuit92 <Jo...@techpursuit.net>
+Riku Voipio <ri...@linaro.org>
+Rob Figueiredo <ro...@gmail.com>
+Soge Zhang <zh...@gmail.com>
+Tiffany Jernigan <ti...@intel.com>
+Tilak Sharma <ti...@google.com>
+Travis Cline <tr...@gmail.com>
+Tudor Golubenco <tu...@gmail.com>
+Yukang <mo...@gmail.com>
+bronze1man <br...@gmail.com>
+debrando <de...@gmail.com>
+henrikedwards <he...@gmail.com>
+\u94c1\u54e5 <gu...@gmail.com>

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/CHANGELOG.md
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/CHANGELOG.md b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/CHANGELOG.md
new file mode 100644
index 0000000..675fab9
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/CHANGELOG.md
@@ -0,0 +1,291 @@
+# Changelog
+
+## v1.3.0 / 2016-04-19
+
+* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
+
+## v1.2.10 / 2016-03-02
+
+* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
+
+## v1.2.9 / 2016-01-13
+
+kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
+
+## v1.2.8 / 2015-12-17
+
+* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
+* inotify: fix race in test
+* enable race detection for continuous integration (Linux, Mac, Windows)
+
+## v1.2.5 / 2015-10-17
+
+* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
+* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
+* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
+* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
+
+## v1.2.1 / 2015-10-14
+
+* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
+
+## v1.2.0 / 2015-02-08
+
+* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
+* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
+* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
+
+## v1.1.1 / 2015-02-05
+
+* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
+
+## v1.1.0 / 2014-12-12
+
+* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
+    * add low-level functions
+    * only need to store flags on directories
+    * less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
+    * done can be an unbuffered channel
+    * remove calls to os.NewSyscallError
+* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
+* kqueue: fix regression in  rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
+* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
+
+## v1.0.4 / 2014-09-07
+
+* kqueue: add dragonfly to the build tags.
+* Rename source code files, rearrange code so exported APIs are at the top.
+* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
+
+## v1.0.3 / 2014-08-19
+
+* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
+
+## v1.0.2 / 2014-08-17
+
+* [Fix] Missing create events on OS X. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
+* [Fix] Make ./path and path equivalent. (thanks @zhsso)
+
+## v1.0.0 / 2014-08-15
+
+* [API] Remove AddWatch on Windows, use Add.
+* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
+* Minor updates based on feedback from golint.
+
+## dev / 2014-07-09
+
+* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
+* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
+
+## dev / 2014-07-04
+
+* kqueue: fix incorrect mutex used in Close()
+* Update example to demonstrate usage of Op.
+
+## dev / 2014-06-28
+
+* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
+* Fix for String() method on Event (thanks Alex Brainman)
+* Don't build on Plan 9 or Solaris (thanks @4ad)
+
+## dev / 2014-06-21
+
+* Events channel of type Event rather than *Event.
+* [internal] use syscall constants directly for inotify and kqueue.
+* [internal] kqueue: rename events to kevents and fileEvent to event.
+
+## dev / 2014-06-19
+
+* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
+* [internal] remove cookie from Event struct (unused).
+* [internal] Event struct has the same definition across every OS.
+* [internal] remove internal watch and removeWatch methods.
+
+## dev / 2014-06-12
+
+* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
+* [API] Pluralized channel names: Events and Errors.
+* [API] Renamed FileEvent struct to Event.
+* [API] Op constants replace methods like IsCreate().
+
+## dev / 2014-06-12
+
+* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
+
+## dev / 2014-05-23
+
+* [API] Remove current implementation of WatchFlags.
+    * current implementation doesn't take advantage of OS for efficiency
+    * provides little benefit over filtering events as they are received, but has  extra bookkeeping and mutexes
+    * no tests for the current implementation
+    * not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
+
+## v0.9.3 / 2014-12-31
+
+* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
+
+## v0.9.2 / 2014-08-17
+
+* [Backport] Fix missing create events on OS X. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
+
+## v0.9.1 / 2014-06-12
+
+* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
+
+## v0.9.0 / 2014-01-17
+
+* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
+* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
+* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
+
+## v0.8.12 / 2013-11-13
+
+* [API] Remove FD_SET and friends from Linux adapter
+
+## v0.8.11 / 2013-11-02
+
+* [Doc] Add Changelog [#72][] (thanks @nathany)
+* [Doc] Spotlight and double modify events on OS X [#62][] (reported by @paulhammond)
+
+## v0.8.10 / 2013-10-19
+
+* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
+* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
+* [Doc] specify OS-specific limits in README (thanks @debrando)
+
+## v0.8.9 / 2013-09-08
+
+* [Doc] Contributing (thanks @nathany)
+* [Doc] update package path in example code [#63][] (thanks @paulhammond)
+* [Doc] GoCI badge in README (Linux only) [#60][]
+* [Doc] Cross-platform testing with Vagrant  [#59][] (thanks @nathany)
+
+## v0.8.8 / 2013-06-17
+
+* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
+
+## v0.8.7 / 2013-06-03
+
+* [API] Make syscall flags internal
+* [Fix] inotify: ignore event changes
+* [Fix] race in symlink test [#45][] (reported by @srid)
+* [Fix] tests on Windows
+* lower case error messages
+
+## v0.8.6 / 2013-05-23
+
+* kqueue: Use EVT_ONLY flag on Darwin
+* [Doc] Update README with full example
+
+## v0.8.5 / 2013-05-09
+
+* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
+
+## v0.8.4 / 2013-04-07
+
+* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
+
+## v0.8.3 / 2013-03-13
+
+* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
+* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
+
+## v0.8.2 / 2013-02-07
+
+* [Doc] add Authors
+* [Fix] fix data races for map access [#29][] (thanks @fsouza)
+
+## v0.8.1 / 2013-01-09
+
+* [Fix] Windows path separators
+* [Doc] BSD License
+
+## v0.8.0 / 2012-11-09
+
+* kqueue: directory watching improvements (thanks @vmirage)
+* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
+* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
+
+## v0.7.4 / 2012-10-09
+
+* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
+* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
+* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
+* [Fix] kqueue: modify after recreation of file
+
+## v0.7.3 / 2012-09-27
+
+* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
+* [Fix] kqueue: no longer get duplicate CREATE events
+
+## v0.7.2 / 2012-09-01
+
+* kqueue: events for created directories
+
+## v0.7.1 / 2012-07-14
+
+* [Fix] for renaming files
+
+## v0.7.0 / 2012-07-02
+
+* [Feature] FSNotify flags
+* [Fix] inotify: Added file name back to event path
+
+## v0.6.0 / 2012-06-06
+
+* kqueue: watch files after directory created (thanks @tmc)
+
+## v0.5.1 / 2012-05-22
+
+* [Fix] inotify: remove all watches before Close()
+
+## v0.5.0 / 2012-05-03
+
+* [API] kqueue: return errors during watch instead of sending over channel
+* kqueue: match symlink behavior on Linux
+* inotify: add `DELETE_SELF` (requested by @taralx)
+* [Fix] kqueue: handle EINTR (reported by @robfig)
+* [Doc] Godoc example [#1][] (thanks @davecheney)
+
+## v0.4.0 / 2012-03-30
+
+* Go 1 released: build with go tool
+* [Feature] Windows support using winfsnotify
+* Windows does not have attribute change notifications
+* Roll attribute notifications into IsModify
+
+## v0.3.0 / 2012-02-19
+
+* kqueue: add files when watch directory
+
+## v0.2.0 / 2011-12-30
+
+* update to latest Go weekly code
+
+## v0.1.0 / 2011-10-19
+
+* kqueue: add watch on file creation to match inotify
+* kqueue: create file event
+* inotify: ignore `IN_IGNORED` events
+* event String()
+* linux: common FileEvent functions
+* initial commit
+
+[#79]: https://github.com/howeyc/fsnotify/pull/79
+[#77]: https://github.com/howeyc/fsnotify/pull/77
+[#72]: https://github.com/howeyc/fsnotify/issues/72
+[#71]: https://github.com/howeyc/fsnotify/issues/71
+[#70]: https://github.com/howeyc/fsnotify/issues/70
+[#63]: https://github.com/howeyc/fsnotify/issues/63
+[#62]: https://github.com/howeyc/fsnotify/issues/62
+[#60]: https://github.com/howeyc/fsnotify/issues/60
+[#59]: https://github.com/howeyc/fsnotify/issues/59
+[#49]: https://github.com/howeyc/fsnotify/issues/49
+[#45]: https://github.com/howeyc/fsnotify/issues/45
+[#40]: https://github.com/howeyc/fsnotify/issues/40
+[#36]: https://github.com/howeyc/fsnotify/issues/36
+[#33]: https://github.com/howeyc/fsnotify/issues/33
+[#29]: https://github.com/howeyc/fsnotify/issues/29
+[#25]: https://github.com/howeyc/fsnotify/issues/25
+[#24]: https://github.com/howeyc/fsnotify/issues/24
+[#21]: https://github.com/howeyc/fsnotify/issues/21

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/CONTRIBUTING.md
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/CONTRIBUTING.md b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/CONTRIBUTING.md
new file mode 100644
index 0000000..617e45a
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/CONTRIBUTING.md
@@ -0,0 +1,77 @@
+# Contributing
+
+## Issues
+
+* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues).
+* Please indicate the platform you are using fsnotify on.
+* A code example to reproduce the problem is appreciated.
+
+## Pull Requests
+
+### Contributor License Agreement
+
+fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
+
+Please indicate that you have signed the CLA in your pull request.
+
+### How fsnotify is Developed
+
+* Development is done on feature branches.
+* Tests are run on BSD, Linux, OS X and Windows.
+* Pull requests are reviewed and [applied to master][am] using [hub][].
+  * Maintainers may modify or squash commits rather than asking contributors to.
+* To issue a new release, the maintainers will:
+  * Update the CHANGELOG
+  * Tag a version, which will become available through gopkg.in.
+ 
+### How to Fork
+
+For smooth sailing, always use the original import path. Installing with `go get` makes this easy. 
+
+1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`)
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Ensure everything works and the tests pass (see below)
+4. Commit your changes (`git commit -am 'Add some feature'`)
+
+Contribute upstream:
+
+1. Fork fsnotify on GitHub
+2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
+3. Push to the branch (`git push fork my-new-feature`)
+4. Create a new Pull Request on GitHub
+
+This workflow is [thoroughly explained by Katrina Owen](https://blog.splice.com/contributing-open-source-git-repositories-go/).
+
+### Testing
+
+fsnotify uses build tags to compile different code on Linux, BSD, OS X, and Windows.
+
+Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
+
+To aid in cross-platform testing there is a Vagrantfile for Linux and BSD.
+
+* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/)
+* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder.
+* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
+* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`.
+* When you're done, you will want to halt or destroy the Vagrant boxes.
+
+Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory.
+
+Right now there is no equivalent solution for Windows and OS X, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads).
+
+### Maintainers
+
+Help maintaining fsnotify is welcome. To be a maintainer:
+
+* Submit a pull request and sign the CLA as above.
+* You must be able to run the test suite on Mac, Windows, Linux and BSD.
+
+To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][].
+
+All code changes should be internal pull requests.
+
+Releases are tagged using [Semantic Versioning](http://semver.org/).
+
+[hub]: https://github.com/github/hub
+[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/LICENSE
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/LICENSE b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/LICENSE
new file mode 100644
index 0000000..f21e540
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2012 The Go Authors. All rights reserved.
+Copyright (c) 2012 fsnotify Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/README.md
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/README.md b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/README.md
new file mode 100644
index 0000000..5ebce86
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/README.md
@@ -0,0 +1,50 @@
+# File system notifications for Go
+
+[![GoDoc](https://godoc.org/github.com/fsnotify/fsnotify?status.svg)](https://godoc.org/github.com/fsnotify/fsnotify) [![Go Report Card](https://goreportcard.com/badge/github.com/fsnotify/fsnotify)](https://goreportcard.com/report/github.com/fsnotify/fsnotify) [![Coverage](http://gocover.io/_badge/github.com/fsnotify/fsnotify)](http://gocover.io/github.com/fsnotify/fsnotify) 
+
+fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library. Ensure you have the latest version installed by running:
+
+```console
+go get -u golang.org/x/sys/...
+```
+
+Cross platform: Windows, Linux, BSD and OS X.
+
+|Adapter   |OS        |Status    |
+|----------|----------|----------|
+|inotify   |Linux 2.6.27 or later, Android\*|Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify)|
+|kqueue    |BSD, OS X, iOS\*|Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify)|
+|ReadDirectoryChangesW|Windows|Supported [![Build status](https://ci.appveyor.com/api/projects/status/ivwjubaih4r0udeh/branch/master?svg=true)](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)|
+|FSEvents  |OS X          |[Planned](https://github.com/fsnotify/fsnotify/issues/11)|
+|FEN       |Solaris 11    |[In Progress](https://github.com/fsnotify/fsnotify/issues/12)|
+|fanotify  |Linux 2.6.37+ | |
+|USN Journals |Windows    |[Maybe](https://github.com/fsnotify/fsnotify/issues/53)|
+|Polling   |*All*         |[Maybe](https://github.com/fsnotify/fsnotify/issues/9)|
+
+\* Android and iOS are untested.
+
+Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) for usage. Consult the [Wiki](https://github.com/fsnotify/fsnotify/wiki) for the FAQ and further information.
+
+## API stability
+
+fsnotify is a fork of [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA). 
+
+All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/). Further API changes are [planned](https://github.com/fsnotify/fsnotify/milestones), and will be tagged with a new major revision number.
+
+Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`.
+
+## Contributing
+
+Please refer to [CONTRIBUTING][] before opening an issue or pull request.
+
+## Example
+
+See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go).
+
+[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md
+
+## Related Projects
+
+* [notify](https://github.com/rjeczalik/notify)
+* [fsevents](https://github.com/fsnotify/fsevents)
+

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/example_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/example_test.go b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/example_test.go
new file mode 100644
index 0000000..700502c
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/example_test.go
@@ -0,0 +1,42 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !plan9
+
+package fsnotify_test
+
+import (
+	"log"
+
+	"github.com/fsnotify/fsnotify"
+)
+
+func ExampleNewWatcher() {
+	watcher, err := fsnotify.NewWatcher()
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer watcher.Close()
+
+	done := make(chan bool)
+	go func() {
+		for {
+			select {
+			case event := <-watcher.Events:
+				log.Println("event:", event)
+				if event.Op&fsnotify.Write == fsnotify.Write {
+					log.Println("modified file:", event.Name)
+				}
+			case err := <-watcher.Errors:
+				log.Println("error:", err)
+			}
+		}
+	}()
+
+	err = watcher.Add("/tmp/foo")
+	if err != nil {
+		log.Fatal(err)
+	}
+	<-done
+}



[13/19] incubator-trafficcontrol git commit: Move TM2 to trafficcontrol/traffic_monitor_golang

Posted by ne...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/manager/peer.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/manager/peer.go b/traffic_monitor/experimental/traffic_monitor/manager/peer.go
deleted file mode 100644
index 3b4a209..0000000
--- a/traffic_monitor/experimental/traffic_monitor/manager/peer.go
+++ /dev/null
@@ -1,165 +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 (
-	"fmt"
-	"sort"
-	"strings"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/util"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/config"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/health"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/peer"
-	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/trafficopsdata"
-)
-
-// StartPeerManager listens for peer results, and when it gets one, it adds it to the peerStates list, and optimistically combines the good results into combinedStates
-func StartPeerManager(
-	peerChan <-chan peer.Result,
-	localStates peer.CRStatesThreadsafe,
-	peerStates peer.CRStatesPeersThreadsafe,
-	events health.ThreadsafeEvents,
-	peerOptimistic bool,
-	toData todata.TODataThreadsafe,
-	cfg config.Config,
-) (peer.CRStatesThreadsafe, health.ThreadsafeEvents) {
-	combinedStates := peer.NewCRStatesThreadsafe()
-	overrideMap := map[enum.CacheName]bool{}
-
-	go func() {
-		for peerResult := range peerChan {
-			comparePeerState(events, peerResult, peerStates)
-			peerStates.Set(peerResult)
-			combineCrStates(events, peerOptimistic, peerStates, localStates.Get(), combinedStates, overrideMap, toData)
-			peerResult.PollFinished <- peerResult.PollID
-		}
-	}()
-	return combinedStates, events
-}
-
-func comparePeerState(events health.ThreadsafeEvents, result peer.Result, peerStates peer.CRStatesPeersThreadsafe) {
-	if result.Available != peerStates.GetPeerAvailability(result.ID) {
-		events.Add(health.Event{Time: result.Time, Unix: result.Time.Unix(), Description: util.JoinErrorsString(result.Errors), Name: result.ID.String(), Hostname: result.ID.String(), Type: "Peer", Available: result.Available})
-	}
-}
-
-// TODO JvD: add deliveryservice stuff
-func combineCrStates(events health.ThreadsafeEvents, peerOptimistic bool, peerStates peer.CRStatesPeersThreadsafe, localStates peer.Crstates, combinedStates peer.CRStatesThreadsafe, overrideMap map[enum.CacheName]bool, toData todata.TODataThreadsafe) {
-	toDataCopy := toData.Get()
-
-	for cacheName, localCacheState := range localStates.Caches { // localStates gets pruned when servers are disabled, it's the source of truth
-		var overrideCondition string
-		available := false
-		override := overrideMap[cacheName]
-
-		if localCacheState.IsAvailable {
-			available = true // we don't care about the peers, we got a "good one", and we're optimistic
-
-			if override {
-				overrideCondition = "cleared; healthy locally"
-				overrideMap[cacheName] = false
-			}
-		} else if peerOptimistic {
-			if !peerStates.HasAvailablePeers() {
-				if override {
-					overrideCondition = "irrelevant; no peers online"
-					overrideMap[cacheName] = false
-				}
-			} else {
-				onlineOnPeers := make([]string, 0)
-
-				for peer, peerCrStates := range peerStates.GetCrstates() {
-					if peerStates.GetPeerAvailability(peer) {
-						if peerCrStates.Caches[cacheName].IsAvailable {
-							onlineOnPeers = append(onlineOnPeers, peer.String())
-						}
-					}
-				}
-
-				if len(onlineOnPeers) > 0 {
-					available = true
-
-					if !override {
-						overrideCondition = fmt.Sprintf("detected; healthy on (at least) %s", strings.Join(onlineOnPeers, ", "))
-						overrideMap[cacheName] = true
-					}
-				} else {
-					if override {
-						overrideCondition = "irrelevant; not online on any peers"
-						overrideMap[cacheName] = false
-					}
-				}
-			}
-		}
-
-		if overrideCondition != "" {
-			events.Add(health.Event{Time: time.Now(), Unix: time.Now().Unix(), Description: fmt.Sprintf("Health protocol override condition %s", overrideCondition), Name: cacheName.String(), Hostname: cacheName.String(), Type: toDataCopy.ServerTypes[cacheName].String(), Available: available})
-		}
-
-		combinedStates.SetCache(cacheName, peer.IsAvailable{IsAvailable: available})
-	}
-
-	for deliveryServiceName, localDeliveryService := range localStates.Deliveryservice {
-		deliveryService := peer.Deliveryservice{IsAvailable: false, DisabledLocations: []enum.CacheName{}} // important to initialize DisabledLocations, so JSON is `[]` not `null`
-		if localDeliveryService.IsAvailable {
-			deliveryService.IsAvailable = true
-		}
-		deliveryService.DisabledLocations = localDeliveryService.DisabledLocations
-
-		for peerName, iPeerStates := range peerStates.GetCrstates() {
-			peerDeliveryService, ok := iPeerStates.Deliveryservice[deliveryServiceName]
-			if !ok {
-				log.Warnf("local delivery service %s not found in peer %s\n", deliveryServiceName, peerName)
-				continue
-			}
-			if peerDeliveryService.IsAvailable {
-				deliveryService.IsAvailable = true
-			}
-			deliveryService.DisabledLocations = intersection(deliveryService.DisabledLocations, peerDeliveryService.DisabledLocations)
-		}
-		combinedStates.SetDeliveryService(deliveryServiceName, deliveryService)
-	}
-}
-
-// CacheNameSlice is a slice of cache names, which fulfills the `sort.Interface` interface.
-type CacheNameSlice []enum.CacheName
-
-func (p CacheNameSlice) Len() int           { return len(p) }
-func (p CacheNameSlice) Less(i, j int) bool { return p[i] < p[j] }
-func (p CacheNameSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
-
-// intersection returns strings in both a and b.
-// Note this modifies a and b. Specifically, it sorts them. If that isn't acceptable, pass copies of your real data.
-func intersection(a []enum.CacheName, b []enum.CacheName) []enum.CacheName {
-	sort.Sort(CacheNameSlice(a))
-	sort.Sort(CacheNameSlice(b))
-	c := []enum.CacheName{} // important to initialize, so JSON is `[]` not `null`
-	for _, s := range a {
-		i := sort.Search(len(b), func(i int) bool { return b[i] >= s })
-		if i < len(b) && b[i] == s {
-			c = append(c, s)
-		}
-	}
-	return c
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/manager/stat.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/manager/stat.go b/traffic_monitor/experimental/traffic_monitor/manager/stat.go
deleted file mode 100644
index 50ce052..0000000
--- a/traffic_monitor/experimental/traffic_monitor/manager/stat.go
+++ /dev/null
@@ -1,219 +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 (
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/cache"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/config"
-	ds "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/deliveryservice"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/health"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/peer"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/threadsafe"
-	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/trafficopsdata"
-	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
-)
-
-func pruneHistory(history []cache.Result, limit uint64) []cache.Result {
-	if uint64(len(history)) > limit {
-		history = history[:limit-1]
-	}
-	return history
-}
-
-func getNewCaches(localStates peer.CRStatesThreadsafe, monitorConfigTS TrafficMonitorConfigMapThreadsafe) map[enum.CacheName]struct{} {
-	monitorConfig := monitorConfigTS.Get()
-	caches := map[enum.CacheName]struct{}{}
-	for cacheName := range localStates.GetCaches() {
-		// ONLINE and OFFLINE caches are not polled.
-		// TODO add a function IsPolled() which can be called by this and the monitorConfig func which sets the polling, to prevent updating in one place breaking the other.
-		if ts, ok := monitorConfig.TrafficServer[string(cacheName)]; !ok || ts.Status == "ONLINE" || ts.Status == "OFFLINE" {
-			continue
-		}
-		caches[cacheName] = struct{}{}
-	}
-	return caches
-}
-
-// StartStatHistoryManager fetches the full statistics data from ATS Astats. This includes everything needed for all calculations, such as Delivery Services. This is expensive, though, and may be hard on ATS, so it should poll less often.
-// For a fast 'is it alive' poll, use the Health Result Manager poll.
-// Returns the stat history, the duration between the stat poll for each cache, the last Kbps data, the calculated Delivery Service stats, and the unpolled caches list.
-func StartStatHistoryManager(
-	cacheStatChan <-chan cache.Result,
-	localStates peer.CRStatesThreadsafe,
-	combinedStates peer.CRStatesThreadsafe,
-	toData todata.TODataThreadsafe,
-	cachesChanged <-chan struct{},
-	errorCount threadsafe.Uint,
-	cfg config.Config,
-	monitorConfig TrafficMonitorConfigMapThreadsafe,
-	events health.ThreadsafeEvents,
-) (threadsafe.ResultInfoHistory, threadsafe.ResultStatHistory, threadsafe.CacheKbpses, DurationMapThreadsafe, threadsafe.LastStats, threadsafe.DSStatsReader, threadsafe.UnpolledCaches, threadsafe.CacheAvailableStatus) {
-	statInfoHistory := threadsafe.NewResultInfoHistory()
-	statResultHistory := threadsafe.NewResultStatHistory()
-	statMaxKbpses := threadsafe.NewCacheKbpses()
-	lastStatDurations := NewDurationMapThreadsafe()
-	lastStatEndTimes := map[enum.CacheName]time.Time{}
-	lastStats := threadsafe.NewLastStats()
-	dsStats := threadsafe.NewDSStats()
-	unpolledCaches := threadsafe.NewUnpolledCaches()
-	tickInterval := cfg.StatFlushInterval
-	localCacheStatus := threadsafe.NewCacheAvailableStatus()
-
-	precomputedData := map[enum.CacheName]cache.PrecomputedData{}
-	lastResults := map[enum.CacheName]cache.Result{}
-
-	process := func(results []cache.Result) {
-		processStatResults(results, statInfoHistory, statResultHistory, statMaxKbpses, combinedStates.Get(), lastStats, toData.Get(), errorCount, dsStats, lastStatEndTimes, lastStatDurations, unpolledCaches, monitorConfig.Get(), precomputedData, lastResults, localStates, events, localCacheStatus)
-	}
-
-	go func() {
-		var ticker *time.Ticker
-		<-cachesChanged // wait for the signal that localStates have been set
-		unpolledCaches.SetNewCaches(getNewCaches(localStates, monitorConfig))
-
-		for {
-			var results []cache.Result
-			results = append(results, <-cacheStatChan)
-			if ticker != nil {
-				ticker.Stop()
-			}
-			ticker = time.NewTicker(tickInterval)
-		innerLoop:
-			for {
-				select {
-				case <-cachesChanged:
-					unpolledCaches.SetNewCaches(getNewCaches(localStates, monitorConfig))
-				case <-ticker.C:
-					log.Infof("StatHistoryManager flushing queued results\n")
-					process(results)
-					break innerLoop
-				default:
-					select {
-					case r := <-cacheStatChan:
-						results = append(results, r)
-					default:
-						process(results)
-						break innerLoop
-					}
-				}
-			}
-		}
-	}()
-	return statInfoHistory, statResultHistory, statMaxKbpses, lastStatDurations, lastStats, &dsStats, unpolledCaches, localCacheStatus
-}
-
-// processStatResults processes the given results, creating and setting DSStats, LastStats, and other stats. Note this is NOT threadsafe, and MUST NOT be called from multiple threads.
-func processStatResults(
-	results []cache.Result,
-	statInfoHistoryThreadsafe threadsafe.ResultInfoHistory,
-	statResultHistoryThreadsafe threadsafe.ResultStatHistory,
-	statMaxKbpsesThreadsafe threadsafe.CacheKbpses,
-	combinedStates peer.Crstates,
-	lastStats threadsafe.LastStats,
-	toData todata.TOData,
-	errorCount threadsafe.Uint,
-	dsStats threadsafe.DSStats,
-	lastStatEndTimes map[enum.CacheName]time.Time,
-	lastStatDurationsThreadsafe DurationMapThreadsafe,
-	unpolledCaches threadsafe.UnpolledCaches,
-	mc to.TrafficMonitorConfigMap,
-	precomputedData map[enum.CacheName]cache.PrecomputedData,
-	lastResults map[enum.CacheName]cache.Result,
-	localStates peer.CRStatesThreadsafe,
-	events health.ThreadsafeEvents,
-	localCacheStatusThreadsafe threadsafe.CacheAvailableStatus,
-) {
-	if len(results) == 0 {
-		return
-	}
-	defer func() {
-		for _, r := range results {
-			// log.Debugf("poll %v %v statfinish\n", result.PollID, endTime)
-			r.PollFinished <- r.PollID
-		}
-	}()
-
-	// setting the statHistory could be put in a goroutine concurrent with `ds.CreateStats`, if it were slow
-	statInfoHistory := statInfoHistoryThreadsafe.Get().Copy()
-	statResultHistory := statResultHistoryThreadsafe.Get().Copy()
-	statMaxKbpses := statMaxKbpsesThreadsafe.Get().Copy()
-
-	for i, result := range results {
-		maxStats := uint64(mc.Profile[mc.TrafficServer[string(result.ID)].Profile].Parameters.HistoryCount)
-		if maxStats < 1 {
-			log.Infof("processStatResults got history count %v for %v, setting to 1\n", maxStats, result.ID)
-			maxStats = 1
-		}
-
-		// TODO determine if we want to add results with errors, or just print the errors now and don't add them.
-		if lastResult, ok := lastResults[result.ID]; ok && result.Error == nil {
-			health.GetVitals(&result, &lastResult, &mc) // TODO precompute
-			if result.Error == nil {
-				results[i] = result
-			} else {
-				log.Errorf("stat poll getting vitals for %v: %v\n", result.ID, result.Error)
-			}
-		}
-		statInfoHistory.Add(result, maxStats)
-		statResultHistory.Add(result, maxStats)
-		// Don't add errored maxes or precomputed DSStats
-		if result.Error == nil {
-			// max and precomputed always contain the latest result from each cache
-			statMaxKbpses.AddMax(result)
-			// if we failed to compute the OutBytes, keep the outbytes of the last result.
-			if result.PrecomputedData.OutBytes == 0 {
-				result.PrecomputedData.OutBytes = precomputedData[result.ID].OutBytes
-			}
-			precomputedData[result.ID] = result.PrecomputedData
-
-		}
-		lastResults[result.ID] = result
-	}
-	statInfoHistoryThreadsafe.Set(statInfoHistory)
-	statResultHistoryThreadsafe.Set(statResultHistory)
-	statMaxKbpsesThreadsafe.Set(statMaxKbpses)
-
-	newDsStats, newLastStats, err := ds.CreateStats(precomputedData, toData, combinedStates, lastStats.Get().Copy(), time.Now(), mc, events)
-	if err != nil {
-		errorCount.Inc()
-		log.Errorf("getting deliveryservice: %v\n", err)
-	} else {
-		dsStats.Set(newDsStats)
-		lastStats.Set(newLastStats)
-	}
-
-	health.CalcAvailability(results, "stat", statResultHistory, mc, toData, localCacheStatusThreadsafe, localStates, events)
-
-	endTime := time.Now()
-	lastStatDurations := lastStatDurationsThreadsafe.Get().Copy()
-	for _, result := range results {
-		if lastStatStart, ok := lastStatEndTimes[result.ID]; ok {
-			d := time.Since(lastStatStart)
-			lastStatDurations[result.ID] = d
-		}
-		lastStatEndTimes[result.ID] = endTime
-	}
-	lastStatDurationsThreadsafe.Set(lastStatDurations)
-	unpolledCaches.SetPolled(results, lastStats.Get())
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/peer/crstates.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/peer/crstates.go b/traffic_monitor/experimental/traffic_monitor/peer/crstates.go
deleted file mode 100644
index cac90e1..0000000
--- a/traffic_monitor/experimental/traffic_monitor/peer/crstates.go
+++ /dev/null
@@ -1,233 +0,0 @@
-package peer
-
-/*
- * 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"
-	"sync"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-)
-
-// Crstates includes availability data for caches and delivery services, as gathered and aggregated by this Traffic Monitor. It is designed to be served at an API endpoint primarily for Traffic Routers (Content Router) to consume.
-// TODO rename to `CRStates`
-type Crstates struct {
-	Caches          map[enum.CacheName]IsAvailable               `json:"caches"`
-	Deliveryservice map[enum.DeliveryServiceName]Deliveryservice `json:"deliveryServices"`
-}
-
-// NewCrstates creates a new CR states object, initializing pointer members.
-func NewCrstates() Crstates {
-	return Crstates{
-		Caches:          map[enum.CacheName]IsAvailable{},
-		Deliveryservice: map[enum.DeliveryServiceName]Deliveryservice{},
-	}
-}
-
-// Copy creates a deep copy of this object. It does not mutate, and is thus safe for multiple goroutines.
-func (a Crstates) Copy() Crstates {
-	b := NewCrstates()
-	for k, v := range a.Caches {
-		b.Caches[k] = v
-	}
-	for k, v := range a.Deliveryservice {
-		b.Deliveryservice[k] = v
-	}
-	return b
-}
-
-// CopyDeliveryservices creates a deep copy of the delivery service availability data.. It does not mutate, and is thus safe for multiple goroutines.
-func (a Crstates) CopyDeliveryservices() map[enum.DeliveryServiceName]Deliveryservice {
-	b := map[enum.DeliveryServiceName]Deliveryservice{}
-	for k, v := range a.Deliveryservice {
-		b[k] = v
-	}
-	return b
-}
-
-// CopyCaches creates a deep copy of the cache availability data.. It does not mutate, and is thus safe for multiple goroutines.
-func (a Crstates) CopyCaches() map[enum.CacheName]IsAvailable {
-	b := map[enum.CacheName]IsAvailable{}
-	for k, v := range a.Caches {
-		b[k] = v
-	}
-	return b
-}
-
-// IsAvailable contains whether the given cache or delivery service is available. It is designed for JSON serialization, namely in the Traffic Monitor 1.0 API.
-type IsAvailable struct {
-	IsAvailable bool `json:"isAvailable"`
-}
-
-// Deliveryservice contains data about the availability of a particular delivery service, and which caches in that delivery service have been marked as unavailable.
-type Deliveryservice struct {
-	DisabledLocations []enum.CacheName `json:"disabledLocations"`
-	IsAvailable       bool             `json:"isAvailable"`
-}
-
-// CrstatesUnMarshall takes bytes of a JSON string, and unmarshals them into a Crstates object.
-func CrstatesUnMarshall(body []byte) (Crstates, error) {
-	var crStates Crstates
-	err := json.Unmarshal(body, &crStates)
-	return crStates, err
-}
-
-// CrstatesMarshall serializes the given Crstates into bytes.
-func CrstatesMarshall(states Crstates) ([]byte, error) {
-	return json.Marshal(states)
-}
-
-// CRStatesThreadsafe provides safe access for multiple goroutines to read a single Crstates object, with a single goroutine writer.
-// This could be made lock-free, if the performance was necessary
-// TODO add separate locks for Caches and Deliveryservice maps?
-type CRStatesThreadsafe struct {
-	crStates *Crstates
-	m        *sync.RWMutex
-}
-
-// NewCRStatesThreadsafe creates a new CRStatesThreadsafe object safe for multiple goroutine readers and a single writer.
-func NewCRStatesThreadsafe() CRStatesThreadsafe {
-	crs := NewCrstates()
-	return CRStatesThreadsafe{m: &sync.RWMutex{}, crStates: &crs}
-}
-
-// Get returns the internal Crstates object for reading.
-func (t *CRStatesThreadsafe) Get() Crstates {
-	t.m.RLock()
-	defer t.m.RUnlock()
-	return t.crStates.Copy()
-}
-
-// GetDeliveryServices returns the internal Crstates delivery services map for reading.
-// TODO add GetCaches, GetDeliveryservices?
-func (t *CRStatesThreadsafe) GetDeliveryServices() map[enum.DeliveryServiceName]Deliveryservice {
-	t.m.RLock()
-	defer t.m.RUnlock()
-	return t.crStates.CopyDeliveryservices()
-}
-
-// GetCache returns the availability data of the given cache. This does not mutate, and is thus safe for multiple goroutines to call.
-func (t *CRStatesThreadsafe) GetCache(name enum.CacheName) (available IsAvailable, ok bool) {
-	t.m.RLock()
-	available, ok = t.crStates.Caches[name]
-	t.m.RUnlock()
-	return
-}
-
-// GetCaches returns the availability data of all caches. This does not mutate, and is thus safe for multiple goroutines to call.
-func (t *CRStatesThreadsafe) GetCaches() map[enum.CacheName]IsAvailable {
-	t.m.RLock()
-	defer t.m.RUnlock()
-	return t.crStates.CopyCaches()
-}
-
-// GetDeliveryService returns the availability data of the given delivery service. This does not mutate, and is thus safe for multiple goroutines to call.
-func (t *CRStatesThreadsafe) GetDeliveryService(name enum.DeliveryServiceName) (ds Deliveryservice, ok bool) {
-	t.m.RLock()
-	ds, ok = t.crStates.Deliveryservice[name]
-	t.m.RUnlock()
-	return
-}
-
-// SetCache sets the internal availability data for a particular cache.
-func (t *CRStatesThreadsafe) SetCache(cacheName enum.CacheName, available IsAvailable) {
-	t.m.Lock()
-	t.crStates.Caches[cacheName] = available
-	t.m.Unlock()
-}
-
-// DeleteCache deletes the given cache from the internal data.
-func (t *CRStatesThreadsafe) DeleteCache(name enum.CacheName) {
-	t.m.Lock()
-	delete(t.crStates.Caches, name)
-	t.m.Unlock()
-}
-
-// SetDeliveryService sets the availability data for the given delivery service.
-func (t *CRStatesThreadsafe) SetDeliveryService(name enum.DeliveryServiceName, ds Deliveryservice) {
-	t.m.Lock()
-	t.crStates.Deliveryservice[name] = ds
-	t.m.Unlock()
-}
-
-// DeleteDeliveryService deletes the given delivery service from the internal data. This MUST NOT be called by multiple goroutines.
-func (t *CRStatesThreadsafe) DeleteDeliveryService(name enum.DeliveryServiceName) {
-	t.m.Lock()
-	delete(t.crStates.Deliveryservice, name)
-	t.m.Unlock()
-}
-
-// CRStatesPeersThreadsafe provides safe access for multiple goroutines to read a map of Traffic Monitor peers to their returned Crstates, with a single goroutine writer.
-// This could be made lock-free, if the performance was necessary
-type CRStatesPeersThreadsafe struct {
-	crStates   map[enum.TrafficMonitorName]Crstates
-	peerStates map[enum.TrafficMonitorName]bool
-	m          *sync.RWMutex
-}
-
-// NewCRStatesPeersThreadsafe creates a new CRStatesPeers object safe for multiple goroutine readers and a single writer.
-func NewCRStatesPeersThreadsafe() CRStatesPeersThreadsafe {
-	return CRStatesPeersThreadsafe{m: &sync.RWMutex{}, crStates: map[enum.TrafficMonitorName]Crstates{}, peerStates: map[enum.TrafficMonitorName]bool{}}
-}
-
-// GetCrstates returns the internal Traffic Monitor peer Crstates data. This MUST NOT be modified.
-func (t *CRStatesPeersThreadsafe) GetCrstates() map[enum.TrafficMonitorName]Crstates {
-	t.m.RLock()
-	m := map[enum.TrafficMonitorName]Crstates{}
-	for k, v := range t.crStates {
-		m[k] = v.Copy()
-	}
-	t.m.RUnlock()
-	return m
-}
-
-// GetPeerAvailability returns the state of the given peer
-func (t *CRStatesPeersThreadsafe) GetPeerAvailability(peer enum.TrafficMonitorName) bool {
-	t.m.RLock()
-	availability := t.peerStates[peer]
-	t.m.RUnlock()
-	return availability
-}
-
-// HasAvailablePeers returns true if at least one peer is online
-func (t *CRStatesPeersThreadsafe) HasAvailablePeers() bool {
-	availablePeers := false
-
-	t.m.RLock()
-
-	for _, available := range t.peerStates {
-		if available {
-			availablePeers = true
-			break
-		}
-	}
-
-	t.m.RUnlock()
-
-	return availablePeers
-}
-
-// Set sets the internal Traffic Monitor peer state and Crstates data. This MUST NOT be called by multiple goroutines.
-func (t *CRStatesPeersThreadsafe) Set(result Result) {
-	t.m.Lock()
-	t.crStates[result.ID] = result.PeerStates
-	t.peerStates[result.ID] = result.Available
-	t.m.Unlock()
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/peer/crstates.json
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/peer/crstates.json b/traffic_monitor/experimental/traffic_monitor/peer/crstates.json
deleted file mode 100644
index d8ffe03..0000000
--- a/traffic_monitor/experimental/traffic_monitor/peer/crstates.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "caches": {
-    "ats-edge-cache-0": {"isAvailable": true},
-    "ats-edge-cache-1": {"isAvailable": false}
-  },
-  "deliveryServices": {
-    "delivery-service-0": {
-      "disabledLocations": [],
-      "isAvailable": true
-    },
-    "delivery-service-1": {
-      "disabledLocations": [],
-      "isAvailable": true
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/peer/peer.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/peer/peer.go b/traffic_monitor/experimental/traffic_monitor/peer/peer.go
deleted file mode 100644
index 66755f8..0000000
--- a/traffic_monitor/experimental/traffic_monitor/peer/peer.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package peer
-
-/*
- * 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"
-	"io"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-)
-
-// Handler handles peer Traffic Monitor data, taking a raw reader, parsing the data, and passing a result object to the ResultChannel. This fulfills the common `Handler` interface.
-type Handler struct {
-	ResultChannel chan Result
-	Notify        int
-}
-
-// NewHandler returns a new peer Handler.
-func NewHandler() Handler {
-	return Handler{ResultChannel: make(chan Result)}
-}
-
-// Result contains the data parsed from polling a peer Traffic Monitor.
-type Result struct {
-	ID           enum.TrafficMonitorName
-	Available    bool
-	Errors       []error
-	PeerStates   Crstates
-	PollID       uint64
-	PollFinished chan<- uint64
-	Time         time.Time
-}
-
-// Handle handles a response from a polled Traffic Monitor peer, parsing the data and forwarding it to the ResultChannel.
-func (handler Handler) Handle(id string, r io.Reader, reqTime time.Duration, err error, pollID uint64, pollFinished chan<- uint64) {
-	result := Result{
-		ID:           enum.TrafficMonitorName(id),
-		Available:    false,
-		Errors:       []error{},
-		PollID:       pollID,
-		PollFinished: pollFinished,
-		Time:         time.Now(),
-	}
-
-	if err != nil {
-		result.Errors = append(result.Errors, err)
-	}
-
-	if r != nil {
-		dec := json.NewDecoder(r)
-		err = dec.Decode(&result.PeerStates)
-
-		if err == nil {
-			result.Available = true
-		} else {
-			result.Errors = append(result.Errors, err)
-		}
-	}
-
-	handler.ResultChannel <- result
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/peer/peer_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/peer/peer_test.go b/traffic_monitor/experimental/traffic_monitor/peer/peer_test.go
deleted file mode 100644
index 627a825..0000000
--- a/traffic_monitor/experimental/traffic_monitor/peer/peer_test.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package peer
-
-/*
- * 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"
-	"io/ioutil"
-	"testing"
-)
-
-func TestCrStates(t *testing.T) {
-	t.Log("Running Peer Tests")
-
-	text, err := ioutil.ReadFile("crstates.json")
-	if err != nil {
-		t.Log(err)
-	}
-	crStates, err := CrstatesUnMarshall(text)
-	if err != nil {
-		t.Log(err)
-	}
-	fmt.Println(len(crStates.Caches), "caches found")
-	for cacheName, crState := range crStates.Caches {
-		t.Logf("%v -> %v", cacheName, crState.IsAvailable)
-	}
-
-	fmt.Println(len(crStates.Deliveryservice), "deliveryservices found")
-	for dsName, deliveryService := range crStates.Deliveryservice {
-		t.Logf("%v -> %v (len:%v)", dsName, deliveryService.IsAvailable, len(deliveryService.DisabledLocations))
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/srvhttp/srvhttp.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/srvhttp/srvhttp.go b/traffic_monitor/experimental/traffic_monitor/srvhttp/srvhttp.go
deleted file mode 100644
index c7c1362..0000000
--- a/traffic_monitor/experimental/traffic_monitor/srvhttp/srvhttp.go
+++ /dev/null
@@ -1,164 +0,0 @@
-package srvhttp
-
-/*
- * 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"
-	"io/ioutil"
-	"net"
-	"net/http"
-	"net/url"
-	"sync"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-	"github.com/hydrogen18/stoppableListener"
-)
-
-// GetCommonAPIData calculates and returns API data common to most endpoints
-func GetCommonAPIData(params url.Values, t time.Time) CommonAPIData {
-	return CommonAPIData{
-		QueryParams: ParametersStr(params),
-		DateStr:     DateStr(t),
-	}
-}
-
-// CommonAPIData contains generic data common to most endpoints.
-type CommonAPIData struct {
-	QueryParams string `json:"pp"`
-	DateStr     string `json:"date"`
-}
-
-// Server is a re-runnable HTTP server. Server.Run() may be called repeatedly, and
-// each time the previous running server will be stopped, and the server will be
-// restarted with the new port address and data request channel.
-type Server struct {
-	stoppableListener          *stoppableListener.StoppableListener
-	stoppableListenerWaitGroup sync.WaitGroup
-}
-
-func (s *Server) registerEndpoints(sm *http.ServeMux, endpoints map[string]http.HandlerFunc, staticFileDir string) error {
-	handleRoot, err := s.handleRootFunc(staticFileDir)
-	if err != nil {
-		return fmt.Errorf("Error getting root endpoint: %v", err)
-	}
-	handleSortableJs, err := s.handleSortableFunc(staticFileDir)
-	if err != nil {
-		return fmt.Errorf("Error getting sortable endpoint: %v", err)
-	}
-
-	for path, f := range endpoints {
-		sm.HandleFunc(path, f)
-	}
-
-	sm.HandleFunc("/", handleRoot)
-	sm.HandleFunc("/sorttable.js", handleSortableJs)
-
-	return nil
-}
-
-// Run runs a new HTTP service at the given addr, making data requests to the given c.
-// Run may be called repeatedly, and each time, will shut down any existing service first.
-// Run is NOT threadsafe, and MUST NOT be called concurrently by multiple goroutines.
-func (s *Server) Run(endpoints map[string]http.HandlerFunc, addr string, readTimeout time.Duration, writeTimeout time.Duration, staticFileDir string) error {
-	if s.stoppableListener != nil {
-		log.Infof("Stopping Web Server\n")
-		s.stoppableListener.Stop()
-		s.stoppableListenerWaitGroup.Wait()
-	}
-	log.Infof("Starting Web Server\n")
-
-	var err error
-	var originalListener net.Listener
-	if originalListener, err = net.Listen("tcp", addr); err != nil {
-		return err
-	}
-	if s.stoppableListener, err = stoppableListener.New(originalListener); err != nil {
-		return err
-	}
-
-	sm := http.NewServeMux()
-	err = s.registerEndpoints(sm, endpoints, staticFileDir)
-	if err != nil {
-		return err
-	}
-	server := &http.Server{
-		Addr:           addr,
-		Handler:        sm,
-		ReadTimeout:    readTimeout,
-		WriteTimeout:   writeTimeout,
-		MaxHeaderBytes: 1 << 20,
-	}
-
-	s.stoppableListenerWaitGroup = sync.WaitGroup{}
-	s.stoppableListenerWaitGroup.Add(1)
-	go func() {
-		defer s.stoppableListenerWaitGroup.Done()
-		err := server.Serve(s.stoppableListener)
-		if err != nil {
-			log.Warnf("HTTP server stopped with error: %v\n", err)
-		}
-	}()
-
-	log.Infof("Web server listening on %s", addr)
-	return nil
-}
-
-// ParametersStr takes the URL query parameters, and returns a string as used by the Traffic Monitor 1.0 endpoints "pp" key.
-func ParametersStr(params url.Values) string {
-	pp := ""
-	for param, vals := range params {
-		for _, val := range vals {
-			pp += param + "=[" + val + "], "
-		}
-	}
-	if len(pp) > 2 {
-		pp = pp[:len(pp)-2]
-	}
-	return pp
-}
-
-//CommonAPIDataDataFormat is a common Date format for the API
-const CommonAPIDataDateFormat = "Mon Jan 02 15:04:05 UTC 2006"
-
-// DateStr returns the given time in the format expected by Traffic Monitor 1.0 API users
-func DateStr(t time.Time) string {
-	return t.UTC().Format(CommonAPIDataDateFormat)
-}
-
-func (s *Server) handleRootFunc(staticFileDir string) (http.HandlerFunc, error) {
-	return s.handleFile(staticFileDir + "/index.html")
-}
-
-func (s *Server) handleSortableFunc(staticFileDir string) (http.HandlerFunc, error) {
-	return s.handleFile(staticFileDir + "/sorttable.js")
-}
-
-func (s *Server) handleFile(name string) (http.HandlerFunc, error) {
-	bytes, err := ioutil.ReadFile(name)
-	if err != nil {
-		return nil, err
-	}
-	contentType := http.DetectContentType(bytes)
-	return func(w http.ResponseWriter, req *http.Request) {
-		w.Header().Set("Content-Type", contentType)
-		fmt.Fprintf(w, "%s", bytes)
-	}, nil
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/static/index.html
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/static/index.html b/traffic_monitor/experimental/traffic_monitor/static/index.html
deleted file mode 100644
index 66cd03b..0000000
--- a/traffic_monitor/experimental/traffic_monitor/static/index.html
+++ /dev/null
@@ -1,560 +0,0 @@
-<!DOCTYPE html>
-
-<!--
-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.
--->
-
-
-<html>
-	<head>
-		<!-- <script src="sorttable.js"></script> -->
-		<meta charset="UTF-8">
-		<title>Traffic Monitor</title>
-		<style>
-		 body {
-			 font-family: "Lato", sans-serif;
-			 font-size: 14px;
-		 }
-
-		 ul.tab {
-			 list-style-type: none;
-			 margin: 0;
-			 padding: 0;
-			 overflow: hidden;
-			 border: 1px solid #ccc;
-			 background-color: #f1f1f1;
-		 }
-
-		 ul.tab li {float: left;}
-
-		 ul.tab li a {
-			 display: inline-block;
-			 color: black;
-			 text-align: center;
-			 padding: 8px 8px;
-			 text-decoration: none;
-			 transition: 0.3s;
-		 }
-
-		 ul.tab li a:hover {
-			 background-color: #cfd;
-		 }
-
-		 .tab-header-selected {
-			 background-color: #adb;
-		 }
-
-		 .tabcontent {
-			 display: none;
-			 padding: 6px 6px;
-			 border: 1px solid #ccc;
-			 border-top: none;
-		 }
-
-		 table, th, td {
-			 border: 0px solid black;
-		 }
-
-		 table {
-			 border-collapse: separate;
-			 border-spacing: 0px 0;
-			 width: 100%;
-		 }
-
-		 th, td {
-			 padding:5px 20px 5px 5px;
-		 }
-
-		 th {
-			 white-space: nowrap;
-		 }
-
-		 tr.stripes:nth-child(even) {
-			 background: #dfe
-		 }
-		 tr.stripes:nth-child(odd) {
-			 background: #fff
-		 }
-
-		 li.endpoint {
-			 margin: 4px 0;
-		 }
-
-		 ul {
-			 list-style: none;
-		 }
-
-		 #top-bar {
-			 border-collapse: collapse;
-			 border-color: #adb;;
-			 border-width: 0px 0px 1px 0px;
-			 padding-bottom: 10px;
-		 }
-
-		 .links {
-			 display: table;
-		 }
-		 .links ul {
-			 display: table-cell;
-			 vertical-align: top;
-		 }
-
-		 .error {
-			 background-color: #f00;
-		 }
-		 .warning {
-			 background-color: #f80;
-		 }
-		</style>
-		<script>
-		 function init() {
-			 openTab('cache-states-content');
-			 getTopBar();
-			 setInterval(getCacheCount, 4755);
-			 setInterval(getCacheAvailableCount, 4800);
-			 setInterval(getBandwidth, 4621);
-			 setInterval(getBandwidthCapacity, 4591);
-			 setInterval(getCacheDownCount, 4832);
-			 setInterval(getVersion, 10007); // change to retry on failure, and only do on startup
-			 setInterval(getTrafficOpsUri, 10019); // change to retry on failure, and only do on startup
-			 setInterval(getTrafficOpsCdn, 10500); // change to retry on failure, and only do on startup
-			 setInterval(getEvents, 2004); // change to retry on failure, and only do on startup
-			 setInterval(getCacheStatuses, 5009);
-			 setInterval(getDsStats, 4003);
-		 }
-
-		 // source: http://stackoverflow.com/a/2901298/292623
-		 function numberStrWithCommas(x) {
-			 return x.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
-		 }
-
-		 function openTab(tabName) {
-			 var i, x, tablinks;
-			 x = document.getElementsByClassName("tabcontent");
-			 for (i = 0; i < x.length; i++) {
-				 x[i].style.display = "none";
-			 }
-			 tablinks = document.getElementsByClassName("tablinks");
-			 for (i = 0; i < x.length; i++) {
-				 tablinks[i].className = tablinks[i].className.replace(" tab-selected", "");
-			 }
-
-			 tabheaders = document.getElementsByClassName("tab-header");
-			 for (i = 0; i < tabheaders.length; i++) {
-				 tabheaders[i].className = tabheaders[i].className.replace(" tab-header-selected", "");
-			 }
-
-			 document.getElementById(tabName).style.display = "block";
-			 document.getElementById(tabName).className += " tab-selected";
-			 document.getElementById(tabName + "-tab").className += " tab-header-selected";
-		 }
-
-		 function getCacheCount() {
-			 ajax("/api/cache-count", function(r) {
-				 document.getElementById("cache-count").innerHTML = r;
-			 });
-		 }
-
-		 function getCacheAvailableCount() {
-			 ajax("/api/cache-available-count", function(r) {
-				 document.getElementById("cache-available").innerHTML = r;
-			 });
-		 }
-
-		 function getBandwidth() {
-			 ajax("/api/bandwidth-kbps", function(r) {
-				 document.getElementById("bandwidth").innerHTML = numberStrWithCommas((parseFloat(r) / kilobitsInGigabit).toFixed(2));
-			 });
-		 }
-
-		 function getBandwidthCapacity() {
-			 ajax("/api/bandwidth-capacity-kbps", function(r) {
-				 document.getElementById("bandwidth-capacity").innerHTML = numberStrWithCommas((r / kilobitsInGigabit).toString());
-			 });
-		 }
-
-		 function getCacheDownCount() {
-			 ajax("/api/cache-down-count", function(r) {
-				 document.getElementById("cache-down").innerHTML = r;
-			 });
-		 }
-
-		 function getVersion() {
-			 ajax("/api/version", function(r) {
-				 document.getElementById("version").innerHTML = r;
-			 });
-		 }
-
-		 function getTrafficOpsUri() {
-			 ajax("/api/traffic-ops-uri", function(r) {
-				 document.getElementById("source-uri").innerHTML = "<a href='" + r + "'>" + r + "</a>";
-			 });
-		 }
-
-
-		 function getTrafficOpsCdn() {
-			 ajax("/publish/ConfigDoc", function(r) {
-				 var j = JSON.parse(r);
-				 document.getElementById("cdn-name").innerHTML = j.cdnName;
-			 });
-		 }
-
-		 var lastEvent = 0;
-		 function getEvents() {
-			 /// \todo add /api/events-since/{index} (and change Traffic Monitor to keep latest
-			 ajax("/publish/EventLog", function(r) {
-				 var jdata = JSON.parse(r);
-				 for (i = jdata.events.length - 1; i >= 0; i--) {
-					 var event = jdata.events[i];
-					 if (event.index <= lastEvent) {
-						 continue;
-					 }
-					 lastEvent = event.index
-					 var row = document.getElementById("event-log").insertRow(1); //document.createElement("tr");
-					 row.classList.add("stripes");
-					 row.insertCell(0).id = row.id + "-name";
-					 document.getElementById(row.id + "-name").textContent = event.name;
-					 document.getElementById(row.id + "-name").style.whiteSpace = "nowrap";
-					 row.insertCell(1).textContent = event.type;
-					 row.insertCell(2).textContent = event.isAvailable ? "available" : "offline";
-					 if(event.isAvailable) {
-						 row.classList.add("stripes");
-						 row.classList.remove("error");
-					 } else {
-						 row.classList.add("error");
-						 row.classList.remove("stripes");
-					 }
-					 row.insertCell(3).textContent = event.description;
-					 row.insertCell(4).id = row.id + "-last";
-					 document.getElementById(row.id + "-last").textContent = new Date(event.time * 1000).toISOString();
-					 document.getElementById(row.id + "-last").style.whiteSpace = "nowrap";
-					 document.getElementById(row.id + "-last").style.textAlign = "right";
-				 }
-			 });
-		 }
-
-		 function getCacheStates() {
-			 ajax("/api/cache-statuses", function(r) {
-				 var jdata = JSON.parse(r);
-				 var servers = Object.keys(jdata); //debug
-				 for (i = 0; i < servers.length; i++) {
-					 var server = servers[i];
-					 if (!document.getElementById("cache-states-" + server)) {
-						 var row = document.getElementById("cache-states").insertRow(1); //document.createElement("tr");
-						 row.classList.add("stripes");
-						 row.id = "cache-states-" + server
-						 row.insertCell(0).id = row.id + "-server";
-						 row.insertCell(1).id = row.id + "-type";
-						 row.insertCell(2).id = row.id + "-status";
-						 row.insertCell(3).id = row.id + "-load-average";
-						 row.insertCell(4).id = row.id + "-query-time";
-						 row.insertCell(5).id = row.id + "-health-time";
-						 row.insertCell(6).id = row.id + "-stat-time";
-						 row.insertCell(7).id = row.id + "-health-span";
-						 row.insertCell(8).id = row.id + "-stat-span";
-						 row.insertCell(9).id = row.id + "-bandwidth";
-						 row.insertCell(10).id = row.id + "-connection-count";
-						 document.getElementById(row.id + "-server").textContent = server;
-						 document.getElementById(row.id + "-server").style.whiteSpace = "nowrap";
-						 document.getElementById(row.id + "-load-average").style.textAlign = "right";
-						 document.getElementById(row.id + "-query-time").style.textAlign = "right";
-						 document.getElementById(row.id + "-health-time").style.textAlign = "right";
-						 document.getElementById(row.id + "-stat-time").style.textAlign = "right";
-						 document.getElementById(row.id + "-health-span").style.textAlign = "right";
-						 document.getElementById(row.id + "-stat-span").style.textAlign = "right";
-						 document.getElementById(row.id + "-bandwidth").style.textAlign = "right";
-						 document.getElementById(row.id + "-connection-count").style.textAlign = "right";
-					 }
-
-					 /* \todo change to iterate over members, and make cells id constructed from these*/
-					 if (jdata[server].hasOwnProperty("type")) {
-						 document.getElementById("cache-states-" + server + "-type").textContent = jdata[server].type;
-					 }
-					 if (jdata[server].hasOwnProperty("status")) {
-						 document.getElementById("cache-states-" + server + "-status").textContent = jdata[server].status;
-						 var row = document.getElementById("cache-states-" + server);
-						 if (jdata[server].status.indexOf("ADMIN_DOWN") != -1) {
-							 row.classList.add("warning");
-							 row.classList.remove("error");
-							 row.classList.remove("stripes");
-						 } else if (jdata[server].status.indexOf(" available") == -1) {
-							 row.classList.add("error");
-							 row.classList.remove("warning");
-							 row.classList.remove("stripes");
-						 } else {
-							 row.classList.add("stripe");
-							 row.classList.remove("warning");
-							 row.classList.remove("error");
-						 }
-					 }
-					 if (jdata[server].hasOwnProperty("load_average")) {
-						 document.getElementById("cache-states-" + server + "-load-average").textContent = jdata[server].load_average;
-					 }
-					 if (jdata[server].hasOwnProperty("query_time_ms")) {
-						 document.getElementById("cache-states-" + server + "-query-time").textContent = jdata[server].query_time_ms;
-					 }
-					 if (jdata[server].hasOwnProperty("health_time_ms")) {
-						 document.getElementById("cache-states-" + server + "-health-time").textContent = jdata[server].health_time_ms;
-					 }
-					 if (jdata[server].hasOwnProperty("stat_time_ms")) {
-						 document.getElementById("cache-states-" + server + "-stat-time").textContent = jdata[server].stat_time_ms;
-					 }
-					 if (jdata[server].hasOwnProperty("health_span_ms")) {
-						 document.getElementById("cache-states-" + server + "-health-span").textContent = jdata[server].health_span_ms;
-					 }
-					 if (jdata[server].hasOwnProperty("stat_span_ms")) {
-						 document.getElementById("cache-states-" + server + "-stat-span").textContent = jdata[server].stat_span_ms;
-					 }
-					 if (jdata[server].hasOwnProperty("bandwidth_kbps")) {
-						 var kbps = (jdata[server].bandwidth_kbps / kilobitsInMegabit).toFixed(2);
-						 var max = numberStrWithCommas((jdata[server].bandwidth_capacity_kbps / kilobitsInMegabit).toFixed(0));
-						 document.getElementById("cache-states-" + server + "-bandwidth").textContent = '' + kbps + ' / ' + max;
-					 } else {
-						 document.getElementById("cache-states-" + server + "-bandwidth").textContent = "N/A";
-					 }
-					 if (jdata[server].hasOwnProperty("connection_count")) {
-						 document.getElementById("cache-states-" + server + "-connection-count").textContent = jdata[server].connection_count;
-					 } else {
-						 document.getElementById("cache-states-" + server + "-connection-count").textContent = "N/A";
-					 }
-				 }
-			 })
-		 }
-
-		 var millisecondsInSecond = 1000;
-		 var kilobitsInGigabit = 1000000;
-		 var kilobitsInMegabit = 1000;
-
-		 // dsDisplayFloat takes a float, and returns the string to display. For nonzero values, it returns two decimal places. For zero values, it returns an empty string, to make nonzero values more visible.
-		 function dsDisplayFloat(f) {
-			 var s = f
-			 if (f != 0.0) {
-				 s = f.toFixed(2);
-			 }
-			 return s
-		 }
-
-		 function getDsStats() {
-			 var now = Date.now();
-
-			 /// \todo add /api/delivery-service-stats which only returns the data needed by the UI, for efficiency
-			 ajax("/publish/DsStats", function(r) {
-				 var j = JSON.parse(r);
-				 var jds = j.deliveryService
-				 var deliveryServiceNames = Object.keys(jds); //debug
-				 //decrementing for loop so DsNames are alphabetical A-Z
-				 //TODO allow for filtering of columns so this isn't necessary
-					for (var i = deliveryServiceNames.length - 1; i >= 0; i--) {
-					 var deliveryService = deliveryServiceNames[i];
-
-					 if (!document.getElementById("deliveryservice-stats-" + deliveryService)) {
-						 var row = document.getElementById("deliveryservice-stats").insertRow(1); //document.createElement("tr");
-						 row.id = "deliveryservice-stats-" + deliveryService
-						 row.insertCell(0).id = row.id + "-delivery-service";
-						 row.insertCell(1).id = row.id + "-status";
-						 row.insertCell(2).id = row.id + "-caches-reporting";
-						 row.insertCell(3).id = row.id + "-bandwidth";
-						 row.insertCell(4).id = row.id + "-tps";
-						 row.insertCell(5).id = row.id + "-2xx";
-						 row.insertCell(6).id = row.id + "-3xx";
-						 row.insertCell(7).id = row.id + "-4xx";
-						 row.insertCell(8).id = row.id + "-5xx";
-						 row.insertCell(9).id = row.id + "-disabled-locations";
-						 document.getElementById(row.id + "-delivery-service").textContent = deliveryService;
-						 document.getElementById(row.id + "-delivery-service").style.whiteSpace = "nowrap";
-						 document.getElementById(row.id + "-caches-reporting").style.textAlign = "right";
-						 document.getElementById(row.id + "-bandwidth").style.textAlign = "right";
-						 document.getElementById(row.id + "-tps").style.textAlign = "right";
-						 document.getElementById(row.id + "-2xx").style.textAlign = "right";
-						 document.getElementById(row.id + "-3xx").style.textAlign = "right";
-						 document.getElementById(row.id + "-4xx").style.textAlign = "right";
-						 document.getElementById(row.id + "-5xx").style.textAlign = "right";
-					 }
-
-					 // \todo check that array has a member before dereferencing [0]
-					 if (jds[deliveryService].hasOwnProperty("isAvailable")) {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-status").textContent = jds[deliveryService]["isAvailable"][0].value == "true" ? "available" : "unavailable - " + jds[deliveryService]["error-string"][0].value;
-					 }
-					 if (jds[deliveryService].hasOwnProperty("caches-reporting") && jds[deliveryService].hasOwnProperty("caches-available") && jds[deliveryService].hasOwnProperty("caches-configured")) {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-caches-reporting").textContent = jds[deliveryService]['caches-reporting'][0].value + " / " + jds[deliveryService]['caches-available'][0].value + " / " + jds[deliveryService]['caches-configured'][0].value;
-					 }
-					 if (jds[deliveryService].hasOwnProperty("total.kbps")) {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-bandwidth").textContent = (jds[deliveryService]['total.kbps'][0].value / kilobitsInMegabit).toFixed(2);
-					 } else {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-bandwidth").textContent = "N/A";
-					 }
-					 if (jds[deliveryService].hasOwnProperty("total.tps_total")) {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-tps").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_total'][0].value));
-					 } else {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-tps").textContent = "N/A";
-					 }
-					 if (jds[deliveryService].hasOwnProperty("total.tps_2xx")) {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-2xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_2xx'][0].value));
-					 } else {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-2xx").textContent = "N/A";
-					 }
-					 if (jds[deliveryService].hasOwnProperty("total.tps_3xx")) {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-3xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_3xx'][0].value));
-					 } else {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-3xx").textContent = "N/A";
-					 }
-					 if (jds[deliveryService].hasOwnProperty("total.tps_4xx")) {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-4xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_4xx'][0].value));
-					 } else {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-4xx").textContent = "N/A";
-					 }
-					 if (jds[deliveryService].hasOwnProperty("total.tps_5xx")) {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-5xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_5xx'][0].value));
-					 } else {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-5xx").textContent = "N/A";
-					 }
-
-					 // \todo implement disabled locations
-
-					 var row = document.getElementById("deliveryservice-stats-" + deliveryService);
-					 if (jds[deliveryService]["isAvailable"][0].value == "true") {
-						 row.classList.add("stripes");
-						 row.classList.remove("error");
-					 } else {
-						 row.classList.add("error");
-						 row.classList.remove("stripes");
-					 }
-				 }
-			 })
-		 }
-
-		 function getCacheStatuses() {
-			 getCacheCount();
-			 getCacheAvailableCount();
-			 getCacheDownCount();
-			 getCacheStates();
-		 }
-
-		 function getTopBar() {
-			 getVersion();
-			 getTrafficOpsUri();
-			 getTrafficOpsCdn();
-			 getCacheStatuses();
-		 }
-
-		 function ajax(endpoint, f) {
-			 var xhttp = new XMLHttpRequest();
-			 xhttp.onreadystatechange = function() {
-				 if (xhttp.readyState == 4 && xhttp.status == 200) {
-					 f(xhttp.responseText);
-				 }
-			 };
-			 xhttp.open("GET", endpoint, true);
-			 xhttp.send();
-		 }
-		</script>
-	</head>
-	<body onload="init()">
-
-		<table id="top-bar">
-			<tr>
-				<td>Caches: count=<span id="cache-count">0</span> available=<span id="cache-available">0</span> down=<span id="cache-down">0</span> </td>
-				<td>Bandwidth: <span id="bandwidth">0</span> / <span id="bandwidth-capacity">\u221e</span> gbps</td>
-				<td>Traffic Ops: <span id="source-uri">unknown</span></td>
-				<td>CDN: <span id="cdn-name">unknown</span></td>
-				<td>Version: <span id="version">unknown</span></td>
-			</tr>
-		</table>
-
-		<div class="links">
-			<ul>
-				<li class="endpoint"><a href="/publish/EventLog">EventLog</a></li>
-				<li class="endpoint"><a href="/publish/CacheStats">CacheStats</a></li>
-				<li class="endpoint"><a href="/publish/DsStats">DsStats</a></li>
-				<li class="endpoint"><a href="/publish/CrStates">CrStates (as published to Traffic Routers)</a></li>
-				<li class="endpoint"><a href="/publish/CrConfig">CrConfig (json)</a></li>
-				<li class="endpoint"><a href="/publish/PeerStates">PeerStates</a></li>
-				<li class="endpoint"><a href="/publish/Stats">Stats</a></li>
-				<li class="endpoint"><a href="/publish/StatSummary">StatSummary</a></li>
-				<li class="endpoint"><a href="/publish/ConfigDoc">ConfigDoc</a></li>
-			</ul>
-
-			<ul>
-				<li class="endpoint"><a href="/api/cache-count">/api/cache-count</a></li>
-				<li class="endpoint"><a href="/api/cache-available-count">/api/cache-available-count</a></li>
-				<li class="endpoint"><a href="/api/cache-down-count">/api/cache-down-count</a></li>
-				<li class="endpoint"><a href="/api/version">/api/version</a></li>
-				<li class="endpoint"><a href="/api/traffic-ops-uri">/api/traffic-ops-uri</a></li>
-				<li class="endpoint"><a href="/api/cache-statuses">/api/cache-statuses</a></li>
-				<li class="endpoint"><a href="/api/bandwidth-kbps">/api/bandwidth-kbps</a></li>
-				<li class="endpoint"><a href="/api/bandwidth-capacity-kbps">/api/bandwidth-capacity-kbps</a></li>
-			</ul>
-		</div>
-
-		<ul class="tab">
-			<li id="cache-states-content-tab" class="tab-header"><a href="#" onclick="openTab('cache-states-content')" class="tablinks">Cache States</a></li>
-			<li id="deliveryservice-stats-content-tab" class="tab-header"><a href="#" onclick="openTab('deliveryservice-stats-content')" class="tablinks">Delivery Service States</a></li>
-			<li id="event-log-content-tab" class="tab-header"><a href="#" onclick="openTab('event-log-content')" class="tablinks">Event Log</a></li>
-		</ul>
-
-		<div id="cache-states-content" class="tabcontent">
-			<table id="cache-states" class="tab-grid sortable">
-				<tr>
-					<th>Server</th>
-					<th>Type</th>
-					<th>Status</th>
-					<th align="right">Load Average</th>
-					<th align="right">Query Time (ms)</th>
-					<th align="right">Health Time (ms)</th>
-					<th align="right">Stat Time (ms)</th>
-					<th align="right">Health Span (ms)</th>
-					<th align="right">Stat Span (ms)</th>
-					<th align="right">Bandwidth (mbps)</th>
-					<th align="right">Connection Count</th>
-				</tr>
-			</table>
-		</div>
-		<div id="deliveryservice-stats-content" class="tabcontent">
-			<table id="deliveryservice-stats" class="tab-grid sortable">
-				<tr>
-					<th>Delivery Service</th>
-					<th>Status</th>
-					<th align="right">Caches Reporting/Available/Configured</th>
-					<th align="right">Bandwidth (mbps)</th>
-					<th align="right">t/sec</th>
-					<th align="right">2xx/sec</th>
-					<th align="right">3xx/sec</th>
-					<th align="right">4xx/sec</th>
-					<th align="right">5xx/sec</th>
-					<th>Disabled Locations</th>
-				</tr>
-			</table>
-		</div>
-
-		<div id="event-log-content" class="tabcontent">
-			<table id="event-log" class="tab-grid sortable">
-				<tr>
-					<th>Name</th>
-					<th>Type</th>
-					<th>Status</th>
-					<th>Description</th>
-					<th align="center" id="event-log-last-header">Event Time</th>
-				</tr>
-			</table>
-		</div>
-
-		<div id="update-num-text">Number of updates: <span id="update-num">0</span></div>
-		<div id="last-val-text">Last Val: <span id="last-val">0</span></div>
-		<a href="/">Refresh Server List</a>
-	</body>
-</html>

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/static/sorttable.js
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/static/sorttable.js b/traffic_monitor/experimental/traffic_monitor/static/sorttable.js
deleted file mode 100644
index 38b0fc6..0000000
--- a/traffic_monitor/experimental/traffic_monitor/static/sorttable.js
+++ /dev/null
@@ -1,495 +0,0 @@
-/*
-  SortTable
-  version 2
-  7th April 2007
-  Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
-
-  Instructions:
-  Download this file
-  Add <script src="sorttable.js"></script> to your HTML
-  Add class="sortable" to any table you'd like to make sortable
-  Click on the headers to sort
-
-  Thanks to many, many people for contributions and suggestions.
-  Licenced as X11: http://www.kryogenix.org/code/browser/licence.html
-  This basically means: do what you want with it.
-*/
-
-
-var stIsIE = /*@cc_on!@*/false;
-
-sorttable = {
-  init: function() {
-    // quit if this function has already been called
-    if (arguments.callee.done) return;
-    // flag this function so we don't do the same thing twice
-    arguments.callee.done = true;
-    // kill the timer
-    if (_timer) clearInterval(_timer);
-
-    if (!document.createElement || !document.getElementsByTagName) return;
-
-    sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/;
-
-    forEach(document.getElementsByTagName('table'), function(table) {
-      if (table.className.search(/\bsortable\b/) != -1) {
-        sorttable.makeSortable(table);
-      }
-    });
-
-  },
-
-  makeSortable: function(table) {
-    if (table.getElementsByTagName('thead').length == 0) {
-      // table doesn't have a tHead. Since it should have, create one and
-      // put the first table row in it.
-      the = document.createElement('thead');
-      the.appendChild(table.rows[0]);
-      table.insertBefore(the,table.firstChild);
-    }
-    // Safari doesn't support table.tHead, sigh
-    if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0];
-
-    if (table.tHead.rows.length != 1) return; // can't cope with two header rows
-
-    // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as
-    // "total" rows, for example). This is B&R, since what you're supposed
-    // to do is put them in a tfoot. So, if there are sortbottom rows,
-    // for backwards compatibility, move them to tfoot (creating it if needed).
-    sortbottomrows = [];
-    for (var i=0; i<table.rows.length; i++) {
-      if (table.rows[i].className.search(/\bsortbottom\b/) != -1) {
-        sortbottomrows[sortbottomrows.length] = table.rows[i];
-      }
-    }
-    if (sortbottomrows) {
-      if (table.tFoot == null) {
-        // table doesn't have a tfoot. Create one.
-        tfo = document.createElement('tfoot');
-        table.appendChild(tfo);
-      }
-      for (var i=0; i<sortbottomrows.length; i++) {
-        tfo.appendChild(sortbottomrows[i]);
-      }
-      delete sortbottomrows;
-    }
-
-    // work through each column and calculate its type
-    headrow = table.tHead.rows[0].cells;
-    for (var i=0; i<headrow.length; i++) {
-      // manually override the type with a sorttable_type attribute
-      if (!headrow[i].className.match(/\bsorttable_nosort\b/)) { // skip this col
-        mtch = headrow[i].className.match(/\bsorttable_([a-z0-9]+)\b/);
-        if (mtch) { override = mtch[1]; }
-	      if (mtch && typeof sorttable["sort_"+override] == 'function') {
-	        headrow[i].sorttable_sortfunction = sorttable["sort_"+override];
-	      } else {
-	        headrow[i].sorttable_sortfunction = sorttable.guessType(table,i);
-	      }
-	      // make it clickable to sort
-	      headrow[i].sorttable_columnindex = i;
-	      headrow[i].sorttable_tbody = table.tBodies[0];
-	      dean_addEvent(headrow[i],"click", sorttable.innerSortFunction = function(e) {
-
-          if (this.className.search(/\bsorttable_sorted\b/) != -1) {
-            // if we're already sorted by this column, just
-            // reverse the table, which is quicker
-            sorttable.reverse(this.sorttable_tbody);
-            this.className = this.className.replace('sorttable_sorted',
-                                                    'sorttable_sorted_reverse');
-            this.removeChild(document.getElementById('sorttable_sortfwdind'));
-            sortrevind = document.createElement('span');
-            sortrevind.id = "sorttable_sortrevind";
-            sortrevind.innerHTML = stIsIE ? '&nbsp<font face="webdings">5</font>' : '&nbsp;&#x25B4;';
-            this.appendChild(sortrevind);
-            return;
-          }
-          if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) {
-            // if we're already sorted by this column in reverse, just
-            // re-reverse the table, which is quicker
-            sorttable.reverse(this.sorttable_tbody);
-            this.className = this.className.replace('sorttable_sorted_reverse',
-                                                    'sorttable_sorted');
-            this.removeChild(document.getElementById('sorttable_sortrevind'));
-            sortfwdind = document.createElement('span');
-            sortfwdind.id = "sorttable_sortfwdind";
-            sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
-            this.appendChild(sortfwdind);
-            return;
-          }
-
-          // remove sorttable_sorted classes
-          theadrow = this.parentNode;
-          forEach(theadrow.childNodes, function(cell) {
-            if (cell.nodeType == 1) { // an element
-              cell.className = cell.className.replace('sorttable_sorted_reverse','');
-              cell.className = cell.className.replace('sorttable_sorted','');
-            }
-          });
-          sortfwdind = document.getElementById('sorttable_sortfwdind');
-          if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); }
-          sortrevind = document.getElementById('sorttable_sortrevind');
-          if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); }
-
-          this.className += ' sorttable_sorted';
-          sortfwdind = document.createElement('span');
-          sortfwdind.id = "sorttable_sortfwdind";
-          sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
-          this.appendChild(sortfwdind);
-
-	        // build an array to sort. This is a Schwartzian transform thing,
-	        // i.e., we "decorate" each row with the actual sort key,
-	        // sort based on the sort keys, and then put the rows back in order
-	        // which is a lot faster because you only do getInnerText once per row
-	        row_array = [];
-	        col = this.sorttable_columnindex;
-	        rows = this.sorttable_tbody.rows;
-	        for (var j=0; j<rows.length; j++) {
-	          row_array[row_array.length] = [sorttable.getInnerText(rows[j].cells[col]), rows[j]];
-	        }
-	        /* If you want a stable sort, uncomment the following line */
-	        //sorttable.shaker_sort(row_array, this.sorttable_sortfunction);
-	        /* and comment out this one */
-	        row_array.sort(this.sorttable_sortfunction);
-
-	        tb = this.sorttable_tbody;
-	        for (var j=0; j<row_array.length; j++) {
-	          tb.appendChild(row_array[j][1]);
-	        }
-
-	        delete row_array;
-	      });
-	    }
-    }
-  },
-
-  guessType: function(table, column) {
-    // guess the type of a column based on its first non-blank row
-    sortfn = sorttable.sort_alpha;
-    for (var i=0; i<table.tBodies[0].rows.length; i++) {
-      text = sorttable.getInnerText(table.tBodies[0].rows[i].cells[column]);
-      if (text != '') {
-        if (text.match(/^-?[\ufffd$\ufffd]?[\d,.]+%?$/)) {
-          return sorttable.sort_numeric;
-        }
-        // check for a date: dd/mm/yyyy or dd/mm/yy
-        // can have / or . or - as separator
-        // can be mm/dd as well
-        possdate = text.match(sorttable.DATE_RE)
-        if (possdate) {
-          // looks like a date
-          first = parseInt(possdate[1]);
-          second = parseInt(possdate[2]);
-          if (first > 12) {
-            // definitely dd/mm
-            return sorttable.sort_ddmm;
-          } else if (second > 12) {
-            return sorttable.sort_mmdd;
-          } else {
-            // looks like a date, but we can't tell which, so assume
-            // that it's dd/mm (English imperialism!) and keep looking
-            sortfn = sorttable.sort_ddmm;
-          }
-        }
-      }
-    }
-    return sortfn;
-  },
-
-  getInnerText: function(node) {
-    // gets the text we want to use for sorting for a cell.
-    // strips leading and trailing whitespace.
-    // this is *not* a generic getInnerText function; it's special to sorttable.
-    // for example, you can override the cell text with a customkey attribute.
-    // it also gets .value for <input> fields.
-
-    if (!node) return "";
-
-    hasInputs = (typeof node.getElementsByTagName == 'function') &&
-                 node.getElementsByTagName('input').length;
-
-    if (node.getAttribute("sorttable_customkey") != null) {
-      return node.getAttribute("sorttable_customkey");
-    }
-    else if (typeof node.textContent != 'undefined' && !hasInputs) {
-      return node.textContent.replace(/^\s+|\s+$/g, '');
-    }
-    else if (typeof node.innerText != 'undefined' && !hasInputs) {
-      return node.innerText.replace(/^\s+|\s+$/g, '');
-    }
-    else if (typeof node.text != 'undefined' && !hasInputs) {
-      return node.text.replace(/^\s+|\s+$/g, '');
-    }
-    else {
-      switch (node.nodeType) {
-        case 3:
-          if (node.nodeName.toLowerCase() == 'input') {
-            return node.value.replace(/^\s+|\s+$/g, '');
-          }
-        case 4:
-          return node.nodeValue.replace(/^\s+|\s+$/g, '');
-          break;
-        case 1:
-        case 11:
-          var innerText = '';
-          for (var i = 0; i < node.childNodes.length; i++) {
-            innerText += sorttable.getInnerText(node.childNodes[i]);
-          }
-          return innerText.replace(/^\s+|\s+$/g, '');
-          break;
-        default:
-          return '';
-      }
-    }
-  },
-
-  reverse: function(tbody) {
-    // reverse the rows in a tbody
-    newrows = [];
-    for (var i=0; i<tbody.rows.length; i++) {
-      newrows[newrows.length] = tbody.rows[i];
-    }
-    for (var i=newrows.length-1; i>=0; i--) {
-       tbody.appendChild(newrows[i]);
-    }
-    delete newrows;
-  },
-
-  /* sort functions
-     each sort function takes two parameters, a and b
-     you are comparing a[0] and b[0] */
-  sort_numeric: function(a,b) {
-    aa = parseFloat(a[0].replace(/[^0-9.-]/g,''));
-    if (isNaN(aa)) aa = 0;
-    bb = parseFloat(b[0].replace(/[^0-9.-]/g,''));
-    if (isNaN(bb)) bb = 0;
-    return aa-bb;
-  },
-  sort_alpha: function(a,b) {
-    if (a[0]==b[0]) return 0;
-    if (a[0]<b[0]) return -1;
-    return 1;
-  },
-  sort_ddmm: function(a,b) {
-    mtch = a[0].match(sorttable.DATE_RE);
-    y = mtch[3]; m = mtch[2]; d = mtch[1];
-    if (m.length == 1) m = '0'+m;
-    if (d.length == 1) d = '0'+d;
-    dt1 = y+m+d;
-    mtch = b[0].match(sorttable.DATE_RE);
-    y = mtch[3]; m = mtch[2]; d = mtch[1];
-    if (m.length == 1) m = '0'+m;
-    if (d.length == 1) d = '0'+d;
-    dt2 = y+m+d;
-    if (dt1==dt2) return 0;
-    if (dt1<dt2) return -1;
-    return 1;
-  },
-  sort_mmdd: function(a,b) {
-    mtch = a[0].match(sorttable.DATE_RE);
-    y = mtch[3]; d = mtch[2]; m = mtch[1];
-    if (m.length == 1) m = '0'+m;
-    if (d.length == 1) d = '0'+d;
-    dt1 = y+m+d;
-    mtch = b[0].match(sorttable.DATE_RE);
-    y = mtch[3]; d = mtch[2]; m = mtch[1];
-    if (m.length == 1) m = '0'+m;
-    if (d.length == 1) d = '0'+d;
-    dt2 = y+m+d;
-    if (dt1==dt2) return 0;
-    if (dt1<dt2) return -1;
-    return 1;
-  },
-
-  shaker_sort: function(list, comp_func) {
-    // A stable sort function to allow multi-level sorting of data
-    // see: http://en.wikipedia.org/wiki/Cocktail_sort
-    // thanks to Joseph Nahmias
-    var b = 0;
-    var t = list.length - 1;
-    var swap = true;
-
-    while(swap) {
-        swap = false;
-        for(var i = b; i < t; ++i) {
-            if ( comp_func(list[i], list[i+1]) > 0 ) {
-                var q = list[i]; list[i] = list[i+1]; list[i+1] = q;
-                swap = true;
-            }
-        } // for
-        t--;
-
-        if (!swap) break;
-
-        for(var i = t; i > b; --i) {
-            if ( comp_func(list[i], list[i-1]) < 0 ) {
-                var q = list[i]; list[i] = list[i-1]; list[i-1] = q;
-                swap = true;
-            }
-        } // for
-        b++;
-
-    } // while(swap)
-  }
-}
-
-/* ******************************************************************
-   Supporting functions: bundled here to avoid depending on a library
-   ****************************************************************** */
-
-// Dean Edwards/Matthias Miller/John Resig
-
-/* for Mozilla/Opera9 */
-if (document.addEventListener) {
-    document.addEventListener("DOMContentLoaded", sorttable.init, false);
-}
-
-/* for Internet Explorer */
-/*@cc_on @*/
-/*@if (@_win32)
-    document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
-    var script = document.getElementById("__ie_onload");
-    script.onreadystatechange = function() {
-        if (this.readyState == "complete") {
-            sorttable.init(); // call the onload handler
-        }
-    };
-/*@end @*/
-
-/* for Safari */
-if (/WebKit/i.test(navigator.userAgent)) { // sniff
-    var _timer = setInterval(function() {
-        if (/loaded|complete/.test(document.readyState)) {
-            sorttable.init(); // call the onload handler
-        }
-    }, 10);
-}
-
-/* for other browsers */
-window.onload = sorttable.init;
-
-// written by Dean Edwards, 2005
-// with input from Tino Zijdel, Matthias Miller, Diego Perini
-
-// http://dean.edwards.name/weblog/2005/10/add-event/
-
-function dean_addEvent(element, type, handler) {
-	if (element.addEventListener) {
-		element.addEventListener(type, handler, false);
-	} else {
-		// assign each event handler a unique ID
-		if (!handler.$$guid) handler.$$guid = dean_addEvent.guid++;
-		// create a hash table of event types for the element
-		if (!element.events) element.events = {};
-		// create a hash table of event handlers for each element/event pair
-		var handlers = element.events[type];
-		if (!handlers) {
-			handlers = element.events[type] = {};
-			// store the existing event handler (if there is one)
-			if (element["on" + type]) {
-				handlers[0] = element["on" + type];
-			}
-		}
-		// store the event handler in the hash table
-		handlers[handler.$$guid] = handler;
-		// assign a global event handler to do all the work
-		element["on" + type] = handleEvent;
-	}
-};
-// a counter used to create unique IDs
-dean_addEvent.guid = 1;
-
-function removeEvent(element, type, handler) {
-	if (element.removeEventListener) {
-		element.removeEventListener(type, handler, false);
-	} else {
-		// delete the event handler from the hash table
-		if (element.events && element.events[type]) {
-			delete element.events[type][handler.$$guid];
-		}
-	}
-};
-
-function handleEvent(event) {
-	var returnValue = true;
-	// grab the event object (IE uses a global event object)
-	event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
-	// get a reference to the hash table of event handlers
-	var handlers = this.events[event.type];
-	// execute each event handler
-	for (var i in handlers) {
-		this.$$handleEvent = handlers[i];
-		if (this.$$handleEvent(event) === false) {
-			returnValue = false;
-		}
-	}
-	return returnValue;
-};
-
-function fixEvent(event) {
-	// add W3C standard event methods
-	event.preventDefault = fixEvent.preventDefault;
-	event.stopPropagation = fixEvent.stopPropagation;
-	return event;
-};
-fixEvent.preventDefault = function() {
-	this.returnValue = false;
-};
-fixEvent.stopPropagation = function() {
-  this.cancelBubble = true;
-}
-
-// Dean's forEach: http://dean.edwards.name/base/forEach.js
-/*
-	forEach, version 1.0
-	Copyright 2006, Dean Edwards
-	License: http://www.opensource.org/licenses/mit-license.php
-*/
-
-// array-like enumeration
-if (!Array.forEach) { // mozilla already supports this
-	Array.forEach = function(array, block, context) {
-		for (var i = 0; i < array.length; i++) {
-			block.call(context, array[i], i, array);
-		}
-	};
-}
-
-// generic enumeration
-Function.prototype.forEach = function(object, block, context) {
-	for (var key in object) {
-		if (typeof this.prototype[key] == "undefined") {
-			block.call(context, object[key], key, object);
-		}
-	}
-};
-
-// character enumeration
-String.forEach = function(string, block, context) {
-	Array.forEach(string.split(""), function(chr, index) {
-		block.call(context, chr, index, string);
-	});
-};
-
-// globally resolve forEach enumeration
-var forEach = function(object, block, context) {
-	if (object) {
-		var resolve = Object; // default
-		if (object instanceof Function) {
-			// functions have a "length" property
-			resolve = Function;
-		} else if (object.forEach instanceof Function) {
-			// the object implements a custom forEach method so use that
-			object.forEach(block, context);
-			return;
-		} else if (typeof object == "string") {
-			// the object is a string
-			resolve = String;
-		} else if (typeof object.length == "number") {
-			// the object is array-like
-			resolve = Array;
-		}
-		resolve.forEach(object, block, context);
-	}
-};
-

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/threadsafe/cacheavailablestatus.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/threadsafe/cacheavailablestatus.go b/traffic_monitor/experimental/traffic_monitor/threadsafe/cacheavailablestatus.go
deleted file mode 100644
index edf7b37..0000000
--- a/traffic_monitor/experimental/traffic_monitor/threadsafe/cacheavailablestatus.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package threadsafe
-
-/*
- * 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 (
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/cache"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-	"sync"
-)
-
-// CacheAvailableStatus wraps a map of cache available statuses to be safe for multiple reader goroutines and one writer.
-type CacheAvailableStatus struct {
-	caches *cache.AvailableStatuses
-	m      *sync.RWMutex
-}
-
-// NewCacheAvailableStatus creates and returns a new CacheAvailableStatus, initializing internal pointer values.
-func NewCacheAvailableStatus() CacheAvailableStatus {
-	c := cache.AvailableStatuses(map[enum.CacheName]cache.AvailableStatus{})
-	return CacheAvailableStatus{m: &sync.RWMutex{}, caches: &c}
-}
-
-// Get returns the internal map of cache statuses. The returned map MUST NOT be modified. If modification is necessary, copy.
-func (o *CacheAvailableStatus) Get() cache.AvailableStatuses {
-	o.m.RLock()
-	defer o.m.RUnlock()
-	return *o.caches
-}
-
-// Set sets the internal map of cache availability. This MUST NOT be called by multiple goroutines.
-func (o *CacheAvailableStatus) Set(v cache.AvailableStatuses) {
-	o.m.Lock()
-	*o.caches = v
-	o.m.Unlock()
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/threadsafe/cachemaxkbpses.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/threadsafe/cachemaxkbpses.go b/traffic_monitor/experimental/traffic_monitor/threadsafe/cachemaxkbpses.go
deleted file mode 100644
index 5bb584c..0000000
--- a/traffic_monitor/experimental/traffic_monitor/threadsafe/cachemaxkbpses.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package threadsafe
-
-/*
- * 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 (
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/cache"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-	"sync"
-)
-
-// CacheAvailableStatus wraps a map of cache available statuses to be safe for multiple reader goroutines and one writer.
-type CacheKbpses struct {
-	v *cache.Kbpses
-	m *sync.RWMutex
-}
-
-// NewCacheAvailableStatus creates and returns a new CacheAvailableStatus, initializing internal pointer values.
-func NewCacheKbpses() CacheKbpses {
-	v := cache.Kbpses(map[enum.CacheName]int64{})
-	return CacheKbpses{m: &sync.RWMutex{}, v: &v}
-}
-
-// Get returns the internal map of cache statuses. The returned map MUST NOT be modified. If modification is necessary, copy.
-func (o *CacheKbpses) Get() cache.Kbpses {
-	o.m.RLock()
-	defer o.m.RUnlock()
-	return *o.v
-}
-
-// Set sets the internal map of cache availability. This MUST NOT be called by multiple goroutines.
-func (o *CacheKbpses) Set(v cache.Kbpses) {
-	o.m.Lock()
-	*o.v = v
-	o.m.Unlock()
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/threadsafe/dsstats.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/threadsafe/dsstats.go b/traffic_monitor/experimental/traffic_monitor/threadsafe/dsstats.go
deleted file mode 100644
index a13c01a..0000000
--- a/traffic_monitor/experimental/traffic_monitor/threadsafe/dsstats.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package threadsafe
-
-/*
- * 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 (
-	"sync"
-
-	dsdata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/deliveryservicedata"
-)
-
-// DSStats wraps a deliveryservice.Stats object to be safe for multiple reader goroutines and a single writer.
-type DSStats struct {
-	dsStats *dsdata.Stats
-	m       *sync.RWMutex
-}
-
-// DSStatsReader permits reading of a dsdata.Stats object, but not writing. This is designed so a Stats object can safely be passed to multiple goroutines, without worry one may unsafely write.
-type DSStatsReader interface {
-	Get() dsdata.StatsReadonly
-}
-
-// NewDSStats returns a deliveryservice.Stats object wrapped to be safe for multiple readers and a single writer.
-func NewDSStats() DSStats {
-	s := dsdata.NewStats()
-	return DSStats{m: &sync.RWMutex{}, dsStats: &s}
-}
-
-// Get returns a Stats object safe for reading by multiple goroutines
-func (o *DSStats) Get() dsdata.StatsReadonly {
-	o.m.RLock()
-	defer o.m.RUnlock()
-	return *o.dsStats
-}
-
-// Set sets the internal Stats object. This MUST NOT be called by multiple goroutines.
-func (o *DSStats) Set(newDsStats dsdata.Stats) {
-	o.m.Lock()
-	*o.dsStats = newDsStats
-	o.m.Unlock()
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/threadsafe/lastkbpsstats.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/threadsafe/lastkbpsstats.go b/traffic_monitor/experimental/traffic_monitor/threadsafe/lastkbpsstats.go
deleted file mode 100644
index 610b8a9..0000000
--- a/traffic_monitor/experimental/traffic_monitor/threadsafe/lastkbpsstats.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package threadsafe
-
-/*
- * 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 (
-	"sync"
-
-	dsdata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/deliveryservicedata"
-)
-
-// LastStats wraps a deliveryservice.LastStats object to be safe for multiple readers and one writer.
-type LastStats struct {
-	stats *dsdata.LastStats
-	m     *sync.RWMutex
-}
-
-// NewLastStats returns a wrapped a deliveryservice.LastStats object safe for multiple readers and one writer.
-func NewLastStats() LastStats {
-	s := dsdata.NewLastStats()
-	return LastStats{m: &sync.RWMutex{}, stats: &s}
-}
-
-// Get returns the last KBPS stats object. Callers MUST NOT modify the object. It is not threadsafe for writing. If the object must be modified, callers must call LastStats.Copy() and modify the copy.
-func (o *LastStats) Get() dsdata.LastStats {
-	o.m.RLock()
-	defer o.m.RUnlock()
-	return *o.stats
-}
-
-// Set sets the internal LastStats object. This MUST NOT be called by multiple goroutines.
-func (o *LastStats) Set(s dsdata.LastStats) {
-	o.m.Lock()
-	*o.stats = s
-	o.m.Unlock()
-}



[07/19] incubator-trafficcontrol git commit: Move TM2 to trafficcontrol/traffic_monitor_golang

Posted by ne...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/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
new file mode 100644
index 0000000..5fa3d89
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/manager/datarequest.go
@@ -0,0 +1,1221 @@
+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"
+	"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 srvTRConfig(opsConfig OpsConfigThreadsafe, toSession towrap.ITrafficOpsSession) ([]byte, error) {
+	cdnName := opsConfig.Get().CdnName
+	if toSession == nil {
+		return nil, fmt.Errorf("Unable to connect to Traffic Ops")
+	}
+	if cdnName == "" {
+		return nil, fmt.Errorf("No CDN Configured")
+	}
+	return toSession.CRConfigRaw(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) ([]byte, error) {
+	return getStats(staticAppData, healthPollInterval, lastHealthDurations.Get(), fetchCount.Get(), healthIteration.Get(), errorCount.Get())
+}
+
+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(WrapErr(errorCount, func() ([]byte, 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)
+		}, 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)),
+	}
+	return addTrailingSlashEndpoints(dispatchMap)
+}
+
+// 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
+}
+
+// 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"`
+}
+
+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 getStats(staticAppData StaticAppData, pollingInterval time.Duration, lastHealthTimes map[enum.CacheName]time.Duration, fetchCount uint64, healthIteration uint64, errorCount uint64) ([]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
+
+	return json.Marshal(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{}
+			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
+}
+
+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/594b8517/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
new file mode 100644
index 0000000..46570a0
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/manager/health.go
@@ -0,0 +1,245 @@
+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 (
+	"sync"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/cache"
+	"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/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"
+)
+
+// 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
+// doesn't include all stat data needed for e.g. delivery service calculations.4
+// Returns the last health durations, events, the local cache statuses, and the health result history.
+func StartHealthResultManager(
+	cacheHealthChan <-chan cache.Result,
+	toData todata.TODataThreadsafe,
+	localStates peer.CRStatesThreadsafe,
+	monitorConfig TrafficMonitorConfigMapThreadsafe,
+	peerStates peer.CRStatesPeersThreadsafe,
+	combinedStates peer.CRStatesThreadsafe,
+	fetchCount threadsafe.Uint,
+	errorCount threadsafe.Uint,
+	cfg config.Config,
+	events health.ThreadsafeEvents,
+	localCacheStatus threadsafe.CacheAvailableStatus,
+) (DurationMapThreadsafe, threadsafe.ResultHistory) {
+	lastHealthDurations := NewDurationMapThreadsafe()
+	healthHistory := threadsafe.NewResultHistory()
+	go healthResultManagerListen(
+		cacheHealthChan,
+		toData,
+		localStates,
+		lastHealthDurations,
+		healthHistory,
+		monitorConfig,
+		peerStates,
+		combinedStates,
+		fetchCount,
+		errorCount,
+		events,
+		localCacheStatus,
+		cfg,
+	)
+	return lastHealthDurations, healthHistory
+}
+
+func healthResultManagerListen(
+	cacheHealthChan <-chan cache.Result,
+	toData todata.TODataThreadsafe,
+	localStates peer.CRStatesThreadsafe,
+	lastHealthDurations DurationMapThreadsafe,
+	healthHistory threadsafe.ResultHistory,
+	monitorConfig TrafficMonitorConfigMapThreadsafe,
+	peerStates peer.CRStatesPeersThreadsafe,
+	combinedStates peer.CRStatesThreadsafe,
+	fetchCount threadsafe.Uint,
+	errorCount threadsafe.Uint,
+	events health.ThreadsafeEvents,
+	localCacheStatus threadsafe.CacheAvailableStatus,
+	cfg config.Config,
+) {
+	lastHealthEndTimes := map[enum.CacheName]time.Time{}
+	// This reads at least 1 value from the cacheHealthChan. Then, we loop, and try to read from the channel some more. If there's nothing to read, we hit `default` and process. If there is stuff to read, we read it, then inner-loop trying to read more. If we're continuously reading and the channel is never empty, and we hit the tick time, process anyway even though the channel isn't empty, to prevent never processing (starvation).
+	var ticker *time.Ticker
+
+	process := func(results []cache.Result) {
+		processHealthResult(
+			cacheHealthChan,
+			toData,
+			localStates,
+			lastHealthDurations,
+			monitorConfig,
+			peerStates,
+			combinedStates,
+			fetchCount,
+			errorCount,
+			events,
+			localCacheStatus,
+			lastHealthEndTimes,
+			healthHistory,
+			results,
+			cfg,
+		)
+	}
+
+	for {
+		var results []cache.Result
+		results = append(results, <-cacheHealthChan)
+		if ticker != nil {
+			ticker.Stop()
+		}
+		ticker = time.NewTicker(cfg.HealthFlushInterval)
+	innerLoop:
+		for {
+			select {
+			case <-ticker.C:
+				log.Infof("Health Result Manager flushing queued results\n")
+				process(results)
+				break innerLoop
+			default:
+				select {
+				case r := <-cacheHealthChan:
+					results = append(results, r)
+				default:
+					process(results)
+					break innerLoop
+				}
+			}
+		}
+	}
+}
+
+// processHealthResult processes the given health results, adding their stats to the CacheAvailableStatus. Note this is NOT threadsafe, because it non-atomically gets CacheAvailableStatuses, Events, LastHealthDurations and later updates them. This MUST NOT be called from multiple threads.
+func processHealthResult(
+	cacheHealthChan <-chan cache.Result,
+	toData todata.TODataThreadsafe,
+	localStates peer.CRStatesThreadsafe,
+	lastHealthDurationsThreadsafe DurationMapThreadsafe,
+	monitorConfig TrafficMonitorConfigMapThreadsafe,
+	peerStates peer.CRStatesPeersThreadsafe,
+	combinedStates peer.CRStatesThreadsafe,
+	fetchCount threadsafe.Uint,
+	errorCount threadsafe.Uint,
+	events health.ThreadsafeEvents,
+	localCacheStatusThreadsafe threadsafe.CacheAvailableStatus,
+	lastHealthEndTimes map[enum.CacheName]time.Time,
+	healthHistory threadsafe.ResultHistory,
+	results []cache.Result,
+	cfg config.Config,
+) {
+	if len(results) == 0 {
+		return
+	}
+	defer func() {
+		for _, r := range results {
+			log.Debugf("poll %v %v finish\n", r.PollID, time.Now())
+			r.PollFinished <- r.PollID
+		}
+	}()
+
+	toDataCopy := toData.Get() // create a copy, so the same data used for all processing of this cache health result
+	monitorConfigCopy := monitorConfig.Get()
+	healthHistoryCopy := healthHistory.Get().Copy()
+	for i, healthResult := range results {
+		fetchCount.Inc()
+		var prevResult cache.Result
+		healthResultHistory := healthHistoryCopy[healthResult.ID]
+		if len(healthResultHistory) != 0 {
+			prevResult = healthResultHistory[len(healthResultHistory)-1]
+		}
+
+		if healthResult.Error == nil {
+			health.GetVitals(&healthResult, &prevResult, &monitorConfigCopy)
+			results[i] = healthResult
+		}
+
+		maxHistory := uint64(monitorConfigCopy.Profile[monitorConfigCopy.TrafficServer[string(healthResult.ID)].Profile].Parameters.HistoryCount)
+		if maxHistory < 1 {
+			log.Infof("processHealthResult got history count %v for %v, setting to 1\n", maxHistory, healthResult.ID)
+			maxHistory = 1
+		}
+
+		healthHistoryCopy[healthResult.ID] = pruneHistory(append([]cache.Result{healthResult}, healthHistoryCopy[healthResult.ID]...), maxHistory)
+	}
+
+	health.CalcAvailability(results, "health", nil, monitorConfigCopy, toDataCopy, localCacheStatusThreadsafe, localStates, events)
+
+	healthHistory.Set(healthHistoryCopy)
+	// TODO determine if we should combineCrStates() here
+
+	lastHealthDurations := lastHealthDurationsThreadsafe.Get().Copy()
+	for _, healthResult := range results {
+		if lastHealthStart, ok := lastHealthEndTimes[healthResult.ID]; ok {
+			d := time.Since(lastHealthStart)
+			lastHealthDurations[healthResult.ID] = d
+		}
+		lastHealthEndTimes[healthResult.ID] = time.Now()
+	}
+	lastHealthDurationsThreadsafe.Set(lastHealthDurations)
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/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
new file mode 100644
index 0000000..4dee868
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/manager/manager.go
@@ -0,0 +1,176 @@
+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 (
+	"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"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/poller"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/cache"
+	"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"
+	"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
+}
+
+//
+// Start starts the poller and handler goroutines
+//
+func Start(opsConfigFile string, cfg config.Config, staticAppData StaticAppData) {
+	toSession := towrap.ITrafficOpsSession(towrap.NewTrafficOpsSessionThreadsafe(nil))
+	counters := fetcher.Counters{
+		Success: gmx.NewCounter("fetchSuccess"),
+		Fail:    gmx.NewCounter("fetchFail"),
+		Pending: gmx.NewGauge("fetchPending"),
+	}
+
+	sharedClient := &http.Client{
+		Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
+		Timeout:   cfg.HTTPTimeout,
+	}
+
+	localStates := peer.NewCRStatesThreadsafe()     // this is the local state as discoverer by this traffic_monitor
+	peerStates := peer.NewCRStatesPeersThreadsafe() // each peer's last state is saved in this map
+	fetchCount := threadsafe.NewUint()              // note this is the number of individual caches fetched from, not the number of times all the caches were polled.
+	healthIteration := threadsafe.NewUint()
+	errorCount := threadsafe.NewUint()
+
+	toData := todata.NewThreadsafe()
+
+	cacheHealthHandler := cache.NewHandler()
+	cacheHealthPoller := poller.NewHTTP(cfg.CacheHealthPollingInterval, true, sharedClient, counters, cacheHealthHandler, cfg.HTTPPollNoSleep)
+	cacheStatHandler := cache.NewPrecomputeHandler(toData, peerStates)
+	cacheStatPoller := poller.NewHTTP(cfg.CacheStatPollingInterval, false, sharedClient, counters, cacheStatHandler, cfg.HTTPPollNoSleep)
+	monitorConfigPoller := poller.NewMonitorConfig(cfg.MonitorConfigPollingInterval)
+	peerHandler := peer.NewHandler()
+	peerPoller := poller.NewHTTP(cfg.PeerPollingInterval, false, sharedClient, counters, peerHandler, cfg.HTTPPollNoSleep)
+
+	go monitorConfigPoller.Poll()
+	go cacheHealthPoller.Poll()
+	go cacheStatPoller.Poll()
+	go peerPoller.Poll()
+
+	events := health.NewThreadsafeEvents(cfg.MaxEvents)
+
+	cachesChanged := make(chan struct{})
+
+	monitorConfig := StartMonitorConfigManager(
+		monitorConfigPoller.ConfigChannel,
+		localStates,
+		cacheStatPoller.ConfigChannel,
+		cacheHealthPoller.ConfigChannel,
+		peerPoller.ConfigChannel,
+		cachesChanged,
+		cfg,
+		staticAppData,
+	)
+
+	combinedStates, events := StartPeerManager(
+		peerHandler.ResultChannel,
+		localStates,
+		peerStates,
+		events,
+		cfg.PeerOptimistic, // TODO remove
+		toData,
+		cfg,
+	)
+
+	statInfoHistory, statResultHistory, statMaxKbpses, _, lastKbpsStats, dsStats, unpolledCaches, localCacheStatus := StartStatHistoryManager(
+		cacheStatHandler.ResultChan(),
+		localStates,
+		combinedStates,
+		toData,
+		cachesChanged,
+		errorCount,
+		cfg,
+		monitorConfig,
+		events,
+	)
+
+	lastHealthDurations, healthHistory := StartHealthResultManager(
+		cacheHealthHandler.ResultChan(),
+		toData,
+		localStates,
+		monitorConfig,
+		peerStates,
+		combinedStates,
+		fetchCount,
+		errorCount,
+		cfg,
+		events,
+		localCacheStatus,
+	)
+
+	StartOpsConfigManager(
+		opsConfigFile,
+		toSession,
+		toData,
+		[]chan<- handler.OpsConfig{monitorConfigPoller.OpsConfigChannel},
+		[]chan<- towrap.ITrafficOpsSession{monitorConfigPoller.SessionChannel},
+		localStates,
+		peerStates,
+		combinedStates,
+		statInfoHistory,
+		statResultHistory,
+		statMaxKbpses,
+		healthHistory,
+		lastKbpsStats,
+		dsStats,
+		events,
+		staticAppData,
+		cacheHealthPoller.Config.Interval,
+		lastHealthDurations,
+		fetchCount,
+		healthIteration,
+		errorCount,
+		localCacheStatus,
+		unpolledCaches,
+		monitorConfig,
+		cfg,
+	)
+
+	healthTickListener(cacheHealthPoller.TickChan, healthIteration)
+}
+
+// healthTickListener listens for health ticks, and writes to the health iteration variable. Does not return.
+func healthTickListener(cacheHealthTick <-chan uint64, healthIteration threadsafe.Uint) {
+	for i := range cacheHealthTick {
+		healthIteration.Set(i)
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/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
new file mode 100644
index 0000000..84f397c
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/manager/monitorconfig.go
@@ -0,0 +1,282 @@
+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 (
+	"fmt"
+	"strings"
+	"sync"
+	"time"
+
+	"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/enum"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
+	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,
+	localStates peer.CRStatesThreadsafe,
+	statURLSubscriber chan<- poller.HttpPollerConfig,
+	healthURLSubscriber chan<- poller.HttpPollerConfig,
+	peerURLSubscriber chan<- poller.HttpPollerConfig,
+	cachesChangeSubscriber chan<- struct{},
+	cfg config.Config,
+	staticAppData StaticAppData,
+) TrafficMonitorConfigMapThreadsafe {
+	monitorConfig := NewTrafficMonitorConfigMapThreadsafe()
+	go monitorConfigListen(monitorConfig,
+		monitorConfigPollChan,
+		localStates,
+		statURLSubscriber,
+		healthURLSubscriber,
+		peerURLSubscriber,
+		cachesChangeSubscriber,
+		cfg,
+		staticAppData,
+	)
+	return monitorConfig
+}
+
+// trafficOpsHealthConnectionTimeoutToDuration takes the int from Traffic Ops, which is in milliseconds, and returns a time.Duration
+// TODO change Traffic Ops Client API to a time.Duration
+func trafficOpsHealthConnectionTimeoutToDuration(t int) time.Duration {
+	return time.Duration(t) * time.Millisecond
+}
+
+// trafficOpsPeerPollIntervalToDuration takes the int from Traffic Ops, which is in milliseconds, and returns a time.Duration
+// TODO change Traffic Ops Client API to a time.Duration
+func trafficOpsPeerPollIntervalToDuration(t int) time.Duration {
+	return time.Duration(t) * time.Millisecond
+}
+
+// trafficOpsStatPollIntervalToDuration takes the int from Traffic Ops, which is in milliseconds, and returns a time.Duration
+// TODO change Traffic Ops Client API to a time.Duration
+func trafficOpsStatPollIntervalToDuration(t int) time.Duration {
+	return time.Duration(t) * time.Millisecond
+}
+
+// trafficOpsHealthPollIntervalToDuration takes the int from Traffic Ops, which is in milliseconds, and returns a time.Duration
+// TODO change Traffic Ops Client API to a time.Duration
+func trafficOpsHealthPollIntervalToDuration(t int) time.Duration {
+	return time.Duration(t) * time.Millisecond
+}
+
+var healthPollCount int
+
+// getPollIntervals reads the Traffic Ops Client monitorConfig structure, and parses and returns the health, peer, and stat poll intervals
+func getHealthPeerStatPollIntervals(monitorConfig to.TrafficMonitorConfigMap, cfg config.Config) (time.Duration, time.Duration, time.Duration, error) {
+	peerPollIntervalI, peerPollIntervalExists := monitorConfig.Config["peers.polling.interval"]
+	if !peerPollIntervalExists {
+		return 0, 0, 0, fmt.Errorf("Traffic Ops Monitor config missing 'peers.polling.interval', not setting config changes.\n")
+	}
+	peerPollIntervalInt, peerPollIntervalIsInt := peerPollIntervalI.(float64)
+	if !peerPollIntervalIsInt {
+		return 0, 0, 0, fmt.Errorf("Traffic Ops Monitor config 'peers.polling.interval' value '%v' type %T is not an integer, not setting config changes.\n", peerPollIntervalI, peerPollIntervalI)
+	}
+	peerPollInterval := trafficOpsPeerPollIntervalToDuration(int(peerPollIntervalInt))
+
+	statPollIntervalI, statPollIntervalExists := monitorConfig.Config["health.polling.interval"]
+	if !statPollIntervalExists {
+		return 0, 0, 0, fmt.Errorf("Traffic Ops Monitor config missing 'health.polling.interval', not setting config changes.\n")
+	}
+	statPollIntervalInt, statPollIntervalIsInt := statPollIntervalI.(float64)
+	if !statPollIntervalIsInt {
+		return 0, 0, 0, fmt.Errorf("Traffic Ops Monitor config 'health.polling.interval' value '%v' type %T is not an integer, not setting config changes.\n", statPollIntervalI, statPollIntervalI)
+	}
+	statPollInterval := trafficOpsStatPollIntervalToDuration(int(statPollIntervalInt))
+
+	healthPollIntervalI, healthPollIntervalExists := monitorConfig.Config["heartbeat.polling.interval"]
+	healthPollIntervalInt, healthPollIntervalIsInt := healthPollIntervalI.(float64)
+	if !healthPollIntervalExists {
+		if healthPollCount == 0 { //only log this once
+			log.Warnln("Traffic Ops Monitor config missing 'heartbeat.polling.interval', using health for heartbeat.")
+			healthPollCount++
+		}
+		healthPollIntervalInt = statPollIntervalInt
+	} else if !healthPollIntervalIsInt {
+		log.Warnf("Traffic Ops Monitor config 'heartbeat.polling.interval' value '%v' type %T is not an integer, using health for heartbeat\n", statPollIntervalI, statPollIntervalI)
+		healthPollIntervalInt = statPollIntervalInt
+	}
+	healthPollInterval := trafficOpsHealthPollIntervalToDuration(int(healthPollIntervalInt))
+
+	return healthPollInterval, peerPollInterval, statPollInterval, nil
+}
+
+// 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,
+	monitorConfigPollChan <-chan to.TrafficMonitorConfigMap,
+	localStates peer.CRStatesThreadsafe,
+	statURLSubscriber chan<- poller.HttpPollerConfig,
+	healthURLSubscriber chan<- poller.HttpPollerConfig,
+	peerURLSubscriber chan<- poller.HttpPollerConfig,
+	cachesChangeSubscriber chan<- struct{},
+	cfg config.Config,
+	staticAppData StaticAppData,
+) {
+	for monitorConfig := range monitorConfigPollChan {
+		monitorConfigTS.Set(monitorConfig)
+		healthURLs := map[string]poller.PollConfig{}
+		statURLs := map[string]poller.PollConfig{}
+		peerURLs := map[string]poller.PollConfig{}
+		caches := map[string]string{}
+
+		healthPollInterval, peerPollInterval, statPollInterval, err := getHealthPeerStatPollIntervals(monitorConfig, cfg)
+		if err != nil {
+			continue
+		}
+
+		for _, srv := range monitorConfig.TrafficServer {
+			caches[srv.HostName] = srv.Status
+
+			cacheName := enum.CacheName(srv.HostName)
+
+			srvStatus := enum.CacheStatusFromString(srv.Status)
+			if srvStatus == enum.CacheStatusOnline {
+				localStates.SetCache(cacheName, peer.IsAvailable{IsAvailable: true})
+				continue
+			}
+			if srvStatus == enum.CacheStatusOffline {
+				continue
+			}
+			// seed states with available = false until our polling cycle picks up a result
+			if _, exists := localStates.GetCache(cacheName); !exists {
+				localStates.SetCache(cacheName, peer.IsAvailable{IsAvailable: false})
+			}
+
+			url := monitorConfig.Profile[srv.Profile].Parameters.HealthPollingURL
+			if url == "" {
+				log.Errorf("monitor config server %v profile %v has no polling URL; can't poll", srv.HostName, srv.Profile)
+				continue
+			}
+			r := strings.NewReplacer(
+				"${hostname}", srv.IP,
+				"${interface_name}", srv.InterfaceName,
+				"application=plugin.remap", "application=system",
+				"application=", "application=system",
+			)
+			url = r.Replace(url)
+
+			connTimeout := trafficOpsHealthConnectionTimeoutToDuration(monitorConfig.Profile[srv.Profile].Parameters.HealthConnectionTimeout)
+			healthURLs[srv.HostName] = poller.PollConfig{URL: url, Timeout: connTimeout}
+			r = strings.NewReplacer("application=system", "application=")
+			statURL := r.Replace(url)
+			statURLs[srv.HostName] = poller.PollConfig{URL: statURL, Timeout: connTimeout}
+		}
+
+		for _, srv := range monitorConfig.TrafficMonitor {
+			if srv.HostName == staticAppData.Hostname {
+				continue
+			}
+			if enum.CacheStatusFromString(srv.Status) != enum.CacheStatusOnline {
+				continue
+			}
+			// TODO: the URL should be config driven. -jse
+			url := fmt.Sprintf("http://%s:%d/publish/CrStates?raw", srv.IP, srv.Port)
+			peerURLs[srv.HostName] = poller.PollConfig{URL: url} // TODO determine timeout.
+		}
+
+		statURLSubscriber <- poller.HttpPollerConfig{Urls: statURLs, Interval: statPollInterval}
+		healthURLSubscriber <- poller.HttpPollerConfig{Urls: healthURLs, Interval: healthPollInterval}
+		peerURLSubscriber <- poller.HttpPollerConfig{Urls: peerURLs, Interval: peerPollInterval}
+
+		for cacheName := range localStates.GetCaches() {
+			if _, exists := monitorConfig.TrafficServer[string(cacheName)]; !exists {
+				log.Warnf("Removing %s from localStates", cacheName)
+				localStates.DeleteCache(cacheName)
+			}
+		}
+
+		cachesChangeSubscriber <- struct{}{}
+
+		// TODO because there are multiple writers to localStates.DeliveryService, there is a race condition, where MonitorConfig (this func) and HealthResultManager could write at the same time, and the HealthResultManager could overwrite a delivery service addition or deletion here. Probably the simplest and most performant fix would be a lock-free algorithm using atomic compare-and-swaps.
+		for _, ds := range monitorConfig.DeliveryService {
+			// since caches default to unavailable, also default DS false
+			if _, exists := localStates.GetDeliveryService(enum.DeliveryServiceName(ds.XMLID)); !exists {
+				localStates.SetDeliveryService(enum.DeliveryServiceName(ds.XMLID), peer.Deliveryservice{IsAvailable: false, DisabledLocations: []enum.CacheName{}}) // important to initialize DisabledLocations, so JSON is `[]` not `null`
+			}
+		}
+		for ds := range localStates.GetDeliveryServices() {
+			if _, exists := monitorConfig.DeliveryService[string(ds)]; !exists {
+				localStates.DeleteDeliveryService(ds)
+			}
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/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
new file mode 100644
index 0000000..3b14ad5
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/manager/opsconfig.go
@@ -0,0 +1,186 @@
+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 (
+	"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/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"
+)
+
+// 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(
+	opsConfigFile string,
+	toSession towrap.ITrafficOpsSession,
+	toData todata.TODataThreadsafe,
+	opsConfigChangeSubscribers []chan<- handler.OpsConfig,
+	toChangeSubscribers []chan<- towrap.ITrafficOpsSession,
+	localStates peer.CRStatesThreadsafe,
+	peerStates peer.CRStatesPeersThreadsafe,
+	combinedStates peer.CRStatesThreadsafe,
+	statInfoHistory threadsafe.ResultInfoHistory,
+	statResultHistory threadsafe.ResultStatHistory,
+	statMaxKbpses threadsafe.CacheKbpses,
+	healthHistory threadsafe.ResultHistory,
+	lastStats threadsafe.LastStats,
+	dsStats threadsafe.DSStatsReader,
+	events health.ThreadsafeEvents,
+	staticAppData StaticAppData,
+	healthPollInterval time.Duration,
+	lastHealthDurations DurationMapThreadsafe,
+	fetchCount threadsafe.Uint,
+	healthIteration threadsafe.Uint,
+	errorCount threadsafe.Uint,
+	localCacheStatus threadsafe.CacheAvailableStatus,
+	unpolledCaches threadsafe.UnpolledCaches,
+	monitorConfig TrafficMonitorConfigMapThreadsafe,
+	cfg config.Config,
+) OpsConfigThreadsafe {
+
+	opsConfigFileChannel := make(chan interface{})
+	opsConfigFilePoller := poller.FilePoller{
+		File:          opsConfigFile,
+		ResultChannel: opsConfigFileChannel,
+	}
+
+	opsConfigChannel := make(chan handler.OpsConfig)
+	opsConfigFileHandler := handler.OpsConfigFileHandler{
+		ResultChannel:    opsConfigFilePoller.ResultChannel,
+		OpsConfigChannel: opsConfigChannel,
+	}
+
+	go opsConfigFileHandler.Listen()
+	go opsConfigFilePoller.Poll()
+
+	opsConfig := NewOpsConfigThreadsafe()
+
+	// 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() {
+		httpServer := srvhttp.Server{}
+
+		for newOpsConfig := range opsConfigChannel {
+			var err error
+			opsConfig.Set(newOpsConfig)
+
+			listenAddress := ":80" // default
+
+			if newOpsConfig.HttpListener != "" {
+				listenAddress = newOpsConfig.HttpListener
+			}
+
+			handleErr := func(err error) {
+				errorCount.Inc()
+				log.Errorf("OpsConfigManager: %v\n", err)
+			}
+
+			endpoints := MakeDispatchMap(
+				opsConfig,
+				toSession,
+				localStates,
+				peerStates,
+				combinedStates,
+				statInfoHistory,
+				statResultHistory,
+				statMaxKbpses,
+				healthHistory,
+				dsStats,
+				events,
+				staticAppData,
+				healthPollInterval,
+				lastHealthDurations,
+				fetchCount,
+				healthIteration,
+				errorCount,
+				toData,
+				localCacheStatus,
+				lastStats,
+				unpolledCaches,
+				monitorConfig,
+			)
+			err = httpServer.Run(endpoints, listenAddress, cfg.ServeReadTimeout, cfg.ServeWriteTimeout, cfg.StaticFileDir)
+			if err != nil {
+				handleErr(fmt.Errorf("MonitorConfigPoller: error creating HTTP server: %s\n", err))
+				continue
+			}
+
+			realToSession, err := to.Login(newOpsConfig.Url, newOpsConfig.Username, newOpsConfig.Password, newOpsConfig.Insecure)
+			if err != nil {
+				handleErr(fmt.Errorf("MonitorConfigPoller: error instantiating Session with traffic_ops: %s\n", err))
+				continue
+			}
+			toSession.Set(realToSession)
+
+			if err := toData.Fetch(toSession, newOpsConfig.CdnName); err != nil {
+				handleErr(fmt.Errorf("Error getting Traffic Ops data: %v\n", err))
+				continue
+			}
+
+			// These must be in a goroutine, because the monitorConfigPoller tick sends to a channel this select listens for. Thus, if we block on sends to the monitorConfigPoller, we have a livelock race condition.
+			// More generically, we're using goroutines as an infinite chan buffer, to avoid potential livelocks
+			for _, subscriber := range opsConfigChangeSubscribers {
+				go func(s chan<- handler.OpsConfig) { s <- newOpsConfig }(subscriber)
+			}
+			for _, subscriber := range toChangeSubscribers {
+				go func(s chan<- towrap.ITrafficOpsSession) { s <- toSession }(subscriber)
+			}
+		}
+	}()
+
+	return opsConfig
+}


[02/19] incubator-trafficcontrol git commit: Add TM2 docker build

Posted by ne...@apache.org.
Add TM2 docker build


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

Branch: refs/heads/master
Commit: b36c65ec9b9afd1ddf40d4246811b2683c9a962e
Parents: 594b851
Author: Robert Butts <ro...@gmail.com>
Authored: Thu Jan 26 14:34:40 2017 -0700
Committer: Dave Neuman <ne...@apache.org>
Committed: Mon Jan 30 08:28:46 2017 -0700

----------------------------------------------------------------------
 .../build/Dockerfile-traffic_monitor_golang     | 43 ++++++++++++++++++++
 infrastructure/docker/build/docker-compose.yml  | 11 +++++
 2 files changed, 54 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/b36c65ec/infrastructure/docker/build/Dockerfile-traffic_monitor_golang
----------------------------------------------------------------------
diff --git a/infrastructure/docker/build/Dockerfile-traffic_monitor_golang b/infrastructure/docker/build/Dockerfile-traffic_monitor_golang
new file mode 100644
index 0000000..209d4b7
--- /dev/null
+++ b/infrastructure/docker/build/Dockerfile-traffic_monitor_golang
@@ -0,0 +1,43 @@
+# 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.
+FROM centos:7
+
+MAINTAINER Dan Kirkwood
+
+RUN	yum -y install \
+		git \
+		rpm-build && \
+	yum -y clean all
+
+ENV GITREPO https://github.com/apache/incubator-trafficcontrol
+ENV BRANCH master
+
+WORKDIR /repo
+
+# Go specific
+RUN	yum -y install \
+		golang && \
+	yum -y clean all
+###
+
+VOLUME ./artifacts:/artifacts
+
+ADD run-build.sh .
+
+CMD ./run-build.sh traffic_monitor_golang 2>&1 | tee /artifacts/build-traffic_monitor_golang.log
+
+# vi:syntax=Dockerfile

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/b36c65ec/infrastructure/docker/build/docker-compose.yml
----------------------------------------------------------------------
diff --git a/infrastructure/docker/build/docker-compose.yml b/infrastructure/docker/build/docker-compose.yml
index 879446d..caa490a 100644
--- a/infrastructure/docker/build/docker-compose.yml
+++ b/infrastructure/docker/build/docker-compose.yml
@@ -44,6 +44,17 @@ services:
     volumes:
       - ./artifacts:/artifacts
 
+  traffic_monitor_golang_build:
+    image: traffic_monitor_golang_builder
+    build:
+      dockerfile: Dockerfile-traffic_monitor_golang
+      context: .
+    environment:
+      - GITREPO
+      - BRANCH
+    volumes:
+      - ./artifacts:/artifacts
+
   traffic_ops_build:
     image: traffic_ops_builder
     build:


[15/19] incubator-trafficcontrol git commit: Move TM2 to trafficcontrol/traffic_monitor_golang

Posted by ne...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/deliveryservice/stat.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/deliveryservice/stat.go b/traffic_monitor/experimental/traffic_monitor/deliveryservice/stat.go
deleted file mode 100644
index 90dbe71..0000000
--- a/traffic_monitor/experimental/traffic_monitor/deliveryservice/stat.go
+++ /dev/null
@@ -1,358 +0,0 @@
-package deliveryservice
-
-/*
- * 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"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/util"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/cache"
-	dsdata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/deliveryservicedata"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/health"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/peer"
-	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/trafficopsdata"
-	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
-)
-
-// TODO remove 'ds' and 'stat' from names
-
-func setStaticData(dsStats dsdata.Stats, dsServers map[enum.DeliveryServiceName][]enum.CacheName) dsdata.Stats {
-	for ds, stat := range dsStats.DeliveryService {
-		stat.CommonStats.CachesConfiguredNum.Value = int64(len(dsServers[ds]))
-		dsStats.DeliveryService[ds] = stat // TODO consider changing dsStats.DeliveryService[ds] to a pointer so this kind of thing isn't necessary; possibly more performant, as well
-	}
-	return dsStats
-}
-
-func addAvailableData(dsStats dsdata.Stats, crStates peer.Crstates, serverCachegroups map[enum.CacheName]enum.CacheGroupName, serverDs map[enum.CacheName][]enum.DeliveryServiceName, serverTypes map[enum.CacheName]enum.CacheType, precomputed map[enum.CacheName]cache.PrecomputedData) (dsdata.Stats, error) {
-	for cache, available := range crStates.Caches {
-		cacheGroup, ok := serverCachegroups[cache]
-		if !ok {
-			log.Infof("CreateStats not adding availability data for '%s': not found in Cachegroups\n", cache)
-			continue
-		}
-		deliveryServices, ok := serverDs[cache]
-		if !ok {
-			log.Infof("CreateStats not adding availability data for '%s': not found in DeliveryServices\n", cache)
-			continue
-		}
-		cacheType, ok := serverTypes[cache]
-		if !ok {
-			log.Infof("CreateStats not adding availability data for '%s': not found in Server Types\n", cache)
-			continue
-		}
-
-		for _, deliveryService := range deliveryServices {
-			if deliveryService == "" {
-				log.Errorln("EMPTY addAvailableData DS") // various bugs in other functions can cause this - this will help identify and debug them.
-				continue
-			}
-
-			stat, ok := dsStats.DeliveryService[deliveryService]
-			if !ok {
-				log.Infof("CreateStats not adding availability data for '%s': not found in Stats\n", cache)
-				continue // TODO log warning? Error?
-			}
-
-			if available.IsAvailable {
-				stat.CommonStats.IsAvailable.Value = true
-				stat.CommonStats.IsHealthy.Value = true
-				stat.CommonStats.CachesAvailableNum.Value++
-				cacheGroupStats := stat.CacheGroups[cacheGroup]
-				cacheGroupStats.IsAvailable.Value = true
-				stat.CacheGroups[cacheGroup] = cacheGroupStats
-				stat.TotalStats.IsAvailable.Value = true
-				typeStats := stat.Types[cacheType]
-				typeStats.IsAvailable.Value = true
-				stat.Types[cacheType] = typeStats
-			}
-
-			// TODO fix nested ifs
-			if pc, ok := precomputed[cache]; ok {
-				if pc.Reporting {
-					stat.CommonStats.CachesReporting[cache] = true
-				} else {
-					log.Debugf("no reporting %v %v\n", cache, deliveryService)
-				}
-			} else {
-				log.Debugf("no result for %v %v\n", cache, deliveryService)
-			}
-
-			dsStats.DeliveryService[deliveryService] = stat // TODO Necessary? Remove?
-		}
-	}
-
-	// TODO move to its own func?
-	for dsName, ds := range crStates.Deliveryservice {
-		stat, ok := dsStats.DeliveryService[dsName]
-		if !ok {
-			log.Warnf("CreateStats not adding disabledLocations for '%s': not found in Stats\n", dsName)
-			continue // TODO log warning? Error?
-		}
-
-		// TODO determine if a deep copy is necessary
-		stat.CommonStats.CachesDisabled = make([]string, len(ds.DisabledLocations), len(ds.DisabledLocations))
-		for i, v := range ds.DisabledLocations {
-			stat.CommonStats.CachesDisabled[i] = string(v)
-		}
-		dsStats.DeliveryService[dsName] = stat // TODO Necessary? Remove?
-	}
-
-	return dsStats, nil
-}
-
-func newLastDSStat() dsdata.LastDSStat {
-	return dsdata.LastDSStat{
-		CacheGroups: map[enum.CacheGroupName]dsdata.LastStatsData{},
-		Type:        map[enum.CacheType]dsdata.LastStatsData{},
-		Caches:      map[enum.CacheName]dsdata.LastStatsData{},
-	}
-}
-
-// BytesPerKilobit is the number of bytes in a kilobit.
-const BytesPerKilobit = 125
-
-func addLastStat(lastData dsdata.LastStatData, newStat int64, newStatTime time.Time) (dsdata.LastStatData, error) {
-	if newStat == lastData.Stat {
-		return lastData, nil
-	}
-
-	if newStat < lastData.Stat {
-		return lastData, fmt.Errorf("new stat '%d'@'%v' value less than last stat '%d'@'%v'", lastData.Stat, lastData.Time, newStat, newStatTime)
-	}
-
-	if newStatTime.Before(lastData.Time) {
-		return lastData, fmt.Errorf("new stat '%d'@'%v' time less than last stat '%d'@'%v'", lastData.Stat, lastData.Time, newStat, newStatTime)
-	}
-
-	if lastData.Stat != 0 {
-		lastData.PerSec = float64(newStat-lastData.Stat) / newStatTime.Sub(lastData.Time).Seconds()
-	}
-
-	lastData.Stat = newStat
-	lastData.Time = newStatTime
-	return lastData, nil
-}
-
-func addLastStats(lastData dsdata.LastStatsData, newStats dsdata.StatCacheStats, newStatsTime time.Time) (dsdata.LastStatsData, error) {
-	errs := []error{nil, nil, nil, nil, nil}
-	lastData.Bytes, errs[0] = addLastStat(lastData.Bytes, newStats.OutBytes.Value, newStatsTime)
-	lastData.Status2xx, errs[1] = addLastStat(lastData.Status2xx, newStats.Status2xx.Value, newStatsTime)
-	lastData.Status3xx, errs[2] = addLastStat(lastData.Status3xx, newStats.Status3xx.Value, newStatsTime)
-	lastData.Status4xx, errs[3] = addLastStat(lastData.Status4xx, newStats.Status4xx.Value, newStatsTime)
-	lastData.Status5xx, errs[4] = addLastStat(lastData.Status5xx, newStats.Status5xx.Value, newStatsTime)
-	return lastData, util.JoinErrors(errs)
-}
-
-func addLastStatsToStatCacheStats(s dsdata.StatCacheStats, l dsdata.LastStatsData) dsdata.StatCacheStats {
-	s.Kbps.Value = l.Bytes.PerSec / BytesPerKilobit
-	s.Tps2xx.Value = l.Status2xx.PerSec
-	s.Tps3xx.Value = l.Status3xx.PerSec
-	s.Tps4xx.Value = l.Status4xx.PerSec
-	s.Tps5xx.Value = l.Status5xx.PerSec
-	s.TpsTotal.Value = s.Tps2xx.Value + s.Tps3xx.Value + s.Tps4xx.Value + s.Tps5xx.Value
-	return s
-}
-
-// addLastDSStatTotals takes a LastDSStat with only raw `Caches` data, and calculates and sets the `CacheGroups`, `Type`, and `Total` data, and returns the augmented structure.
-func addLastDSStatTotals(lastStat dsdata.LastDSStat, cachesReporting map[enum.CacheName]bool, serverCachegroups map[enum.CacheName]enum.CacheGroupName, serverTypes map[enum.CacheName]enum.CacheType) dsdata.LastDSStat {
-	cacheGroups := map[enum.CacheGroupName]dsdata.LastStatsData{}
-	cacheTypes := map[enum.CacheType]dsdata.LastStatsData{}
-	total := dsdata.LastStatsData{}
-	for cacheName, cacheStats := range lastStat.Caches {
-		if !cachesReporting[cacheName] {
-			continue
-		}
-
-		if cacheGroup, ok := serverCachegroups[cacheName]; ok {
-			cacheGroups[cacheGroup] = cacheGroups[cacheGroup].Sum(cacheStats)
-		} else {
-			log.Warnf("while computing delivery service data, cache %v not in cachegroups\n", cacheName)
-		}
-
-		if cacheType, ok := serverTypes[cacheName]; ok {
-			cacheTypes[cacheType] = cacheTypes[cacheType].Sum(cacheStats)
-		} else {
-			log.Warnf("while computing delivery service data, cache %v not in types\n", cacheName)
-		}
-		total = total.Sum(cacheStats)
-	}
-	lastStat.CacheGroups = cacheGroups
-	lastStat.Type = cacheTypes
-	lastStat.Total = total
-	return lastStat
-}
-
-// addDSPerSecStats calculates and adds the per-second delivery service stats to both the Stats and LastStats structures, and returns the augmented structures.
-func addDSPerSecStats(dsName enum.DeliveryServiceName, stat dsdata.Stat, lastStats dsdata.LastStats, dsStats dsdata.Stats, dsStatsTime time.Time, serverCachegroups map[enum.CacheName]enum.CacheGroupName, serverTypes map[enum.CacheName]enum.CacheType, mc to.TrafficMonitorConfigMap, events health.ThreadsafeEvents) (dsdata.Stats, dsdata.LastStats) {
-	err := error(nil)
-	lastStat, lastStatExists := lastStats.DeliveryServices[dsName]
-	if !lastStatExists {
-		lastStat = newLastDSStat()
-	}
-
-	for cacheName, cacheStats := range stat.Caches {
-		lastStat.Caches[cacheName], err = addLastStats(lastStat.Caches[cacheName], cacheStats, dsStatsTime)
-		if err != nil {
-			log.Warnf("%v adding kbps for cache %v: %v", dsName, cacheName, err)
-			continue
-		}
-		cacheStats.Kbps.Value = lastStat.Caches[cacheName].Bytes.PerSec / BytesPerKilobit
-		stat.Caches[cacheName] = cacheStats
-	}
-
-	lastStat = addLastDSStatTotals(lastStat, stat.CommonStats.CachesReporting, serverCachegroups, serverTypes)
-
-	for cacheGroup, cacheGroupStat := range lastStat.CacheGroups {
-		stat.CacheGroups[cacheGroup] = addLastStatsToStatCacheStats(stat.CacheGroups[cacheGroup], cacheGroupStat)
-	}
-	for cacheType, cacheTypeStat := range lastStat.Type {
-		stat.Types[cacheType] = addLastStatsToStatCacheStats(stat.Types[cacheType], cacheTypeStat)
-	}
-	stat.TotalStats = addLastStatsToStatCacheStats(stat.TotalStats, lastStat.Total)
-	lastStats.DeliveryServices[dsName] = lastStat
-	errStr := getDsErrString(dsName, stat.TotalStats, mc)
-	if errStr != "" {
-		stat.CommonStats.IsAvailable.Value = false
-		stat.CommonStats.IsHealthy.Value = false
-		stat.CommonStats.ErrorStr.Value = errStr
-		events.Add(health.Event{Time: time.Now(), Unix: time.Now().Unix(), Description: errStr, Name: dsName.String(), Hostname: dsName.String(), Type: "Delivery Service", Available: stat.CommonStats.IsAvailable.Value})
-	}
-
-	dsStats.DeliveryService[dsName] = stat
-	return dsStats, lastStats
-}
-
-// latestBytes returns the most recent OutBytes from the given cache results, and the time of that result. It assumes zero results are not valid, but nonzero results with errors are valid.
-func latestBytes(p cache.PrecomputedData) (int64, time.Time, error) {
-	if p.OutBytes == 0 {
-		return 0, time.Time{}, fmt.Errorf("no valid results")
-	}
-	return p.OutBytes, p.Time, nil
-}
-
-// addCachePerSecStats calculates the cache per-second stats, adds them to LastStats, and returns the augmented object.
-func addCachePerSecStats(cacheName enum.CacheName, precomputed cache.PrecomputedData, lastStats dsdata.LastStats) dsdata.LastStats {
-	outBytes, outBytesTime, err := latestBytes(precomputed) // it's ok if `latestBytes` returns 0s with an error, `addLastStat` will refrain from setting it (unless the previous calculation was nonzero, in which case it will error appropriately).
-	if err != nil {
-		log.Warnf("while computing delivery service data for cache %v: %v\n", cacheName, err)
-	}
-	lastStat := lastStats.Caches[cacheName] // if lastStats.Caches[cacheName] doesn't exist, it will be zero-constructed, and `addLastStat` will refrain from setting the PerSec for zero LastStats
-	lastStat.Bytes, err = addLastStat(lastStat.Bytes, outBytes, outBytesTime)
-	if err != nil {
-		log.Warnf("while computing delivery service data for cache %v: %v\n", cacheName, err)
-		return lastStats
-	}
-	lastStats.Caches[cacheName] = lastStat
-
-	return lastStats
-}
-
-// addPerSecStats adds Kbps fields to the NewStats, based on the previous out_bytes in the oldStats, and the time difference.
-//
-// Traffic Server only updates its data every N seconds. So, often we get a new Stats with the same OutBytes as the previous one,
-// So, we must record the last changed value, and the time it changed. Then, if the new OutBytes is different from the previous,
-// we set the (new - old) / lastChangedTime as the KBPS, and update the recorded LastChangedTime and LastChangedValue
-//
-// TODO handle ATS byte rolling (when the `out_bytes` overflows back to 0)
-func addPerSecStats(precomputed map[enum.CacheName]cache.PrecomputedData, dsStats dsdata.Stats, lastStats dsdata.LastStats, dsStatsTime time.Time, serverCachegroups map[enum.CacheName]enum.CacheGroupName, serverTypes map[enum.CacheName]enum.CacheType, mc to.TrafficMonitorConfigMap, events health.ThreadsafeEvents) (dsdata.Stats, dsdata.LastStats) {
-	for dsName, stat := range dsStats.DeliveryService {
-		dsStats, lastStats = addDSPerSecStats(dsName, stat, lastStats, dsStats, dsStatsTime, serverCachegroups, serverTypes, mc, events)
-	}
-	for cacheName, precomputedData := range precomputed {
-		lastStats = addCachePerSecStats(cacheName, precomputedData, lastStats)
-	}
-
-	return dsStats, lastStats
-}
-
-// CreateStats aggregates and creates statistics from given precomputed stat history. It returns the created stats, information about these stats necessary for the next calculation, and any error.
-func CreateStats(precomputed map[enum.CacheName]cache.PrecomputedData, toData todata.TOData, crStates peer.Crstates, lastStats dsdata.LastStats, now time.Time, mc to.TrafficMonitorConfigMap, events health.ThreadsafeEvents) (dsdata.Stats, dsdata.LastStats, error) {
-	start := time.Now()
-	dsStats := dsdata.NewStats()
-	for deliveryService := range toData.DeliveryServiceServers {
-		if deliveryService == "" {
-			log.Errorf("EMPTY CreateStats deliveryService")
-			continue
-		}
-		dsStats.DeliveryService[deliveryService] = *dsdata.NewStat()
-	}
-	dsStats = setStaticData(dsStats, toData.DeliveryServiceServers)
-	var err error
-	dsStats, err = addAvailableData(dsStats, crStates, toData.ServerCachegroups, toData.ServerDeliveryServices, toData.ServerTypes, precomputed) // TODO move after stat summarisation
-	if err != nil {
-		return dsStats, lastStats, fmt.Errorf("Error getting Cache availability data: %v", err)
-	}
-
-	for server, precomputedData := range precomputed {
-		cachegroup, ok := toData.ServerCachegroups[server]
-		if !ok {
-			log.Warnf("server %s has no cachegroup, skipping\n", server)
-			continue
-		}
-		serverType, ok := toData.ServerTypes[server]
-		if !ok {
-			log.Warnf("server %s not in CRConfig, skipping\n", server)
-			continue
-		}
-
-		// TODO check result.PrecomputedData.Errors
-		for ds, resultStat := range precomputedData.DeliveryServiceStats {
-			if ds == "" {
-				log.Errorf("EMPTY precomputed delivery service")
-				continue
-			}
-
-			if _, ok := dsStats.DeliveryService[ds]; !ok {
-				dsStats.DeliveryService[ds] = resultStat
-				continue
-			}
-			httpDsStat := dsStats.DeliveryService[ds]
-			httpDsStat.TotalStats = httpDsStat.TotalStats.Sum(resultStat.TotalStats)
-			httpDsStat.CacheGroups[cachegroup] = httpDsStat.CacheGroups[cachegroup].Sum(resultStat.CacheGroups[cachegroup])
-			httpDsStat.Types[serverType] = httpDsStat.Types[serverType].Sum(resultStat.Types[serverType])
-			httpDsStat.Caches[server] = httpDsStat.Caches[server].Sum(resultStat.Caches[server])
-			httpDsStat.CachesTimeReceived[server] = resultStat.CachesTimeReceived[server]
-			httpDsStat.CommonStats = dsStats.DeliveryService[ds].CommonStats
-			dsStats.DeliveryService[ds] = httpDsStat // TODO determine if necessary
-		}
-	}
-
-	perSecStats, lastStats := addPerSecStats(precomputed, dsStats, lastStats, now, toData.ServerCachegroups, toData.ServerTypes, mc, events)
-	log.Infof("CreateStats took %v\n", time.Since(start))
-	perSecStats.Time = time.Now()
-	return perSecStats, lastStats, nil
-}
-
-func getDsErrString(dsName enum.DeliveryServiceName, dsStats dsdata.StatCacheStats, monitorConfig to.TrafficMonitorConfigMap) string {
-	tpsThreshold := monitorConfig.DeliveryService[dsName.String()].TotalTPSThreshold
-	if tpsThreshold > 0 && dsStats.TpsTotal.Value > float64(tpsThreshold) {
-		return fmt.Sprintf("total.tps_total too high (%v > %v)", dsStats.TpsTotal.Value, tpsThreshold)
-	}
-
-	kbpsThreshold := monitorConfig.DeliveryService[dsName.String()].TotalKbpsThreshold
-	if kbpsThreshold > 0 && dsStats.Kbps.Value > float64(kbpsThreshold) {
-		return fmt.Sprintf("total.kbps too high (%v > %v)", dsStats.Kbps.Value, kbpsThreshold)
-	}
-	return ""
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/deliveryservicedata/stat.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/deliveryservicedata/stat.go b/traffic_monitor/experimental/traffic_monitor/deliveryservicedata/stat.go
deleted file mode 100644
index b60a3d7..0000000
--- a/traffic_monitor/experimental/traffic_monitor/deliveryservicedata/stat.go
+++ /dev/null
@@ -1,488 +0,0 @@
-package deliveryservicedata // TODO rename?
-
-/*
- * 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 (
-	"errors"
-	"fmt"
-	"net/url"
-	"strconv"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/srvhttp"
-)
-
-// Filter encapsulates functions to filter a given set of Stats, e.g. from HTTP query parameters.
-// TODO combine with cache.Filter?
-type Filter interface {
-	UseStat(name string) bool
-	UseDeliveryService(name enum.DeliveryServiceName) bool
-	WithinStatHistoryMax(int) bool
-}
-
-// StatName is the name of a stat.
-type StatName string
-
-// StatOld is the old JSON representation of a stat, from Traffic Monitor 1.0.
-type StatOld struct {
-	Time  int64       `json:"time"`
-	Value interface{} `json:"value"`
-	Span  int         `json:"span,omitempty"`  // TODO set? remove?
-	Index int         `json:"index,omitempty"` // TODO set? remove?
-}
-
-// StatsOld is the old JSON representation of stats, from Traffic Monitor 1.0. It is designed to be serialized and returns from an API, and includes stat history for each delivery service, as well as data common to most endpoints.
-type StatsOld struct {
-	DeliveryService map[enum.DeliveryServiceName]map[StatName][]StatOld `json:"deliveryService"`
-	srvhttp.CommonAPIData
-}
-
-// StatsReadonly is a read-only interface for delivery service Stats, designed to be passed to multiple goroutine readers.
-type StatsReadonly interface {
-	Get(enum.DeliveryServiceName) (StatReadonly, bool)
-	JSON(Filter, url.Values) StatsOld
-}
-
-// StatReadonly is a read-only interface for a delivery service Stat, designed to be passed to multiple goroutine readers.
-type StatReadonly interface {
-	Copy() Stat
-	Common() StatCommonReadonly
-	CacheGroup(name enum.CacheGroupName) (StatCacheStats, bool)
-	Type(name enum.CacheType) (StatCacheStats, bool)
-	Total() StatCacheStats
-}
-
-// StatCommonReadonly is a read-only interface for a delivery service's common Stat data, designed to be passed to multiple goroutine readers.
-type StatCommonReadonly interface {
-	Copy() StatCommon
-	CachesConfigured() StatInt
-	CachesReportingNames() []enum.CacheName
-	Error() StatString
-	Status() StatString
-	Healthy() StatBool
-	Available() StatBool
-	CachesAvailable() StatInt
-}
-
-// StatMeta includes metadata about a particular stat.
-type StatMeta struct {
-	Time int64 `json:"time"`
-}
-
-// StatFloat is a float stat, combined with its metadata
-type StatFloat struct {
-	StatMeta
-	Value float64 `json:"value"`
-}
-
-// StatBool is a boolean stat, combined with its metadata
-type StatBool struct {
-	StatMeta
-	Value bool `json:"value"`
-}
-
-// StatInt is an integer stat, combined with its metadata
-type StatInt struct {
-	StatMeta
-	Value int64 `json:"value"`
-}
-
-// StatString is a string stat, combined with its metadata
-type StatString struct {
-	StatMeta
-	Value string `json:"value"`
-}
-
-// StatCommon contains stat data common to most delivery service stats.
-type StatCommon struct {
-	CachesConfiguredNum StatInt                 `json:"caches_configured"`
-	CachesReporting     map[enum.CacheName]bool `json:"caches_reporting"`
-	ErrorStr            StatString              `json:"error_string"`
-	StatusStr           StatString              `json:"status"`
-	IsHealthy           StatBool                `json:"is_healthy"`
-	IsAvailable         StatBool                `json:"is_available"`
-	CachesAvailableNum  StatInt                 `json:"caches_available"`
-	CachesDisabled      []string                `json:"disabled_locations"`
-}
-
-// Copy returns a deep copy of this StatCommon object.
-func (a StatCommon) Copy() StatCommon {
-	b := a
-	for k, v := range a.CachesReporting {
-		b.CachesReporting[k] = v
-	}
-	b.CachesDisabled = make([]string, len(a.CachesDisabled), len(a.CachesDisabled))
-	for i, v := range a.CachesDisabled {
-		b.CachesDisabled[i] = v
-	}
-	return b
-}
-
-// CachesConfigured returns the number of caches configured for this delivery service stat. It is part of the StatCommonReadonly interface.
-func (a StatCommon) CachesConfigured() StatInt {
-	return a.CachesConfiguredNum
-}
-
-// CacheReporting returns the number of caches reporting for this delivery service stat. It is part of the StatCommonReadonly interface.
-func (a StatCommon) CacheReporting(name enum.CacheName) (bool, bool) {
-	c, ok := a.CachesReporting[name]
-	return c, ok
-}
-
-// CachesReportingNames returns the list of caches reporting for this delivery service stat. It is part of the StatCommonReadonly interface.
-func (a StatCommon) CachesReportingNames() []enum.CacheName {
-	names := make([]enum.CacheName, 0, len(a.CachesReporting))
-	for name := range a.CachesReporting {
-		names = append(names, name)
-	}
-	return names
-}
-
-// Error returns the error string of this delivery service stat. It is part of the StatCommonReadonly interface.
-func (a StatCommon) Error() StatString {
-	return a.ErrorStr
-}
-
-// Status returns the status string of this delivery service stat. It is part of the StatCommonReadonly interface.
-func (a StatCommon) Status() StatString {
-	return a.StatusStr
-}
-
-// Healthy returns whether this delivery service is considered healthy by this stat. It is part of the StatCommonReadonly interface.
-func (a StatCommon) Healthy() StatBool {
-	return a.IsHealthy
-}
-
-// Available returns whether this delivery service is considered available by this stat. It is part of the StatCommonReadonly interface.
-func (a StatCommon) Available() StatBool {
-	return a.IsAvailable
-}
-
-// CachesAvailable returns the number of caches available to the delivery service in this stat. It is part of the StatCommonReadonly interface.
-func (a StatCommon) CachesAvailable() StatInt {
-	return a.CachesAvailableNum
-}
-
-// StatCacheStats is all the stats generated by a cache.
-// This may also be used for aggregate stats, for example, the summary of all cache stats for a cache group, or delivery service.
-// Each stat is an array, in case there are multiple data points at different times. However, a single data point i.e. a single array member is common.
-type StatCacheStats struct {
-	OutBytes    StatInt    `json:"out_bytes"`
-	IsAvailable StatBool   `json:"is_available"`
-	Status5xx   StatInt    `json:"status_5xx"`
-	Status4xx   StatInt    `json:"status_4xx"`
-	Status3xx   StatInt    `json:"status_3xx"`
-	Status2xx   StatInt    `json:"status_2xx"`
-	InBytes     StatFloat  `json:"in_bytes"`
-	Kbps        StatFloat  `json:"kbps"`
-	Tps5xx      StatFloat  `json:"tps_5xx"`
-	Tps4xx      StatFloat  `json:"tps_4xx"`
-	Tps3xx      StatFloat  `json:"tps_3xx"`
-	Tps2xx      StatFloat  `json:"tps_2xx"`
-	ErrorString StatString `json:"error_string"`
-	TpsTotal    StatFloat  `json:"tps_total"`
-}
-
-// Sum adds the given cache stats to this cache stats. Numeric values are summed; strings are appended.
-func (a StatCacheStats) Sum(b StatCacheStats) StatCacheStats {
-	return StatCacheStats{
-		OutBytes:    StatInt{Value: a.OutBytes.Value + b.OutBytes.Value},
-		IsAvailable: StatBool{Value: a.IsAvailable.Value || b.IsAvailable.Value},
-		Status5xx:   StatInt{Value: a.Status5xx.Value + b.Status5xx.Value},
-		Status4xx:   StatInt{Value: a.Status4xx.Value + b.Status4xx.Value},
-		Status3xx:   StatInt{Value: a.Status3xx.Value + b.Status3xx.Value},
-		Status2xx:   StatInt{Value: a.Status2xx.Value + b.Status2xx.Value},
-		InBytes:     StatFloat{Value: a.InBytes.Value + b.InBytes.Value},
-		Kbps:        StatFloat{Value: a.Kbps.Value + b.Kbps.Value},
-		Tps5xx:      StatFloat{Value: a.Tps5xx.Value + b.Tps5xx.Value},
-		Tps4xx:      StatFloat{Value: a.Tps4xx.Value + b.Tps4xx.Value},
-		Tps3xx:      StatFloat{Value: a.Tps3xx.Value + b.Tps3xx.Value},
-		Tps2xx:      StatFloat{Value: a.Tps2xx.Value + b.Tps2xx.Value},
-		ErrorString: StatString{Value: a.ErrorString.Value + b.ErrorString.Value},
-		TpsTotal:    StatFloat{Value: a.TpsTotal.Value + b.TpsTotal.Value},
-	}
-}
-
-// Stat represents a complete delivery service stat, for a given poll, or at the time requested.
-type Stat struct {
-	CommonStats        StatCommon
-	CacheGroups        map[enum.CacheGroupName]StatCacheStats
-	Types              map[enum.CacheType]StatCacheStats
-	Caches             map[enum.CacheName]StatCacheStats
-	CachesTimeReceived map[enum.CacheName]time.Time
-	TotalStats         StatCacheStats
-}
-
-// ErrNotProcessedStat indicates a stat received is not used by Traffic Monitor, nor returned by any API endpoint. Receiving this error indicates the stat has been discarded.
-var ErrNotProcessedStat = errors.New("This stat is not used.")
-
-// NewStat returns a new delivery service Stat, initializing pointer members.
-func NewStat() *Stat {
-	return &Stat{
-		CacheGroups:        map[enum.CacheGroupName]StatCacheStats{},
-		Types:              map[enum.CacheType]StatCacheStats{},
-		CommonStats:        StatCommon{CachesReporting: map[enum.CacheName]bool{}},
-		Caches:             map[enum.CacheName]StatCacheStats{},
-		CachesTimeReceived: map[enum.CacheName]time.Time{},
-	}
-}
-
-// Copy performs a deep copy of this Stat. It does not modify, and is thus safe for multiple goroutines.
-func (a Stat) Copy() Stat {
-	b := Stat{
-		CommonStats:        a.CommonStats.Copy(),
-		TotalStats:         a.TotalStats,
-		CacheGroups:        map[enum.CacheGroupName]StatCacheStats{},
-		Types:              map[enum.CacheType]StatCacheStats{},
-		Caches:             map[enum.CacheName]StatCacheStats{},
-		CachesTimeReceived: map[enum.CacheName]time.Time{},
-	}
-	for k, v := range a.CacheGroups {
-		b.CacheGroups[k] = v
-	}
-	for k, v := range a.Types {
-		b.Types[k] = v
-	}
-	for k, v := range a.Caches {
-		b.Caches[k] = v
-	}
-	for k, v := range a.CachesTimeReceived {
-		b.CachesTimeReceived[k] = v
-	}
-	return b
-}
-
-// Common returns the common stat data for this stat. It is part of the StatCommonReadonly interface.
-func (a Stat) Common() StatCommonReadonly {
-	return a.CommonStats
-}
-
-// CacheGroup returns the data for the given cachegroup in this stat. It is part of the StatCommonReadonly interface.
-func (a Stat) CacheGroup(name enum.CacheGroupName) (StatCacheStats, bool) {
-	c, ok := a.CacheGroups[name]
-	return c, ok
-}
-
-// Type returns the aggregated data for the given cache type in this stat. It is part of the StatCommonReadonly interface.
-func (a Stat) Type(name enum.CacheType) (StatCacheStats, bool) {
-	t, ok := a.Types[name]
-	return t, ok
-}
-
-// Total returns the aggregated total data in this stat. It is part of the StatCommonReadonly interface.
-func (a Stat) Total() StatCacheStats {
-	return a.TotalStats
-}
-
-// Stats is the JSON-serialisable representation of delivery service Stats. It maps delivery service names to individual stat objects.
-type Stats struct {
-	DeliveryService map[enum.DeliveryServiceName]Stat `json:"deliveryService"`
-	Time            time.Time                         `json:"-"`
-}
-
-// Copy performs a deep copy of this Stats object.
-func (s Stats) Copy() Stats {
-	b := NewStats()
-	for k, v := range s.DeliveryService {
-		b.DeliveryService[k] = v.Copy()
-	}
-	b.Time = s.Time
-	return b
-}
-
-// Get returns the stats for the given delivery service, and whether it exists.
-func (s Stats) Get(name enum.DeliveryServiceName) (StatReadonly, bool) {
-	ds, ok := s.DeliveryService[name]
-	return ds, ok
-}
-
-// JSON returns an object formatted as expected to be serialized to JSON and served.
-func (s Stats) JSON(filter Filter, params url.Values) StatsOld {
-	// TODO fix to be the time calculated, not the time requested
-	now := s.Time.UnixNano() / int64(time.Millisecond) // Traffic Monitor 1.0 API is 'ms since the epoch'
-	jsonObj := &StatsOld{
-		CommonAPIData:   srvhttp.GetCommonAPIData(params, time.Now()),
-		DeliveryService: map[enum.DeliveryServiceName]map[StatName][]StatOld{},
-	}
-
-	for deliveryService, stat := range s.DeliveryService {
-		if !filter.UseDeliveryService(deliveryService) {
-			continue
-		}
-		jsonObj.DeliveryService[deliveryService] = map[StatName][]StatOld{}
-		jsonObj = addCommonData(jsonObj, &stat.CommonStats, deliveryService, now, filter)
-		for cacheGroup, cacheGroupStats := range stat.CacheGroups {
-			jsonObj = addStatCacheStats(jsonObj, cacheGroupStats, deliveryService, "location."+string(cacheGroup)+".", now, filter)
-		}
-		for cacheType, typeStats := range stat.Types {
-			jsonObj = addStatCacheStats(jsonObj, typeStats, deliveryService, "type."+cacheType.String()+".", now, filter)
-		}
-		jsonObj = addStatCacheStats(jsonObj, stat.TotalStats, deliveryService, "total.", now, filter)
-	}
-	return *jsonObj
-}
-
-// NewStats creates a new Stats object, initializing any pointer members.
-// TODO rename to just 'New'?
-func NewStats() Stats {
-	return Stats{DeliveryService: map[enum.DeliveryServiceName]Stat{}}
-}
-
-// LastStats includes the previously recieved stats for DeliveryServices and Caches, the stat itself, when it was received, and the stat value per second.
-type LastStats struct {
-	DeliveryServices map[enum.DeliveryServiceName]LastDSStat
-	Caches           map[enum.CacheName]LastStatsData
-}
-
-// NewLastStats returns a new LastStats object, initializing internal pointer values.
-func NewLastStats() LastStats {
-	return LastStats{DeliveryServices: map[enum.DeliveryServiceName]LastDSStat{}, Caches: map[enum.CacheName]LastStatsData{}}
-}
-
-// Copy performs a deep copy of this LastStats object.
-func (a LastStats) Copy() LastStats {
-	b := NewLastStats()
-	for k, v := range a.DeliveryServices {
-		b.DeliveryServices[k] = v.Copy()
-	}
-	for k, v := range a.Caches {
-		b.Caches[k] = v
-	}
-	return b
-}
-
-// LastDSStat maps and aggregates the last stats received for the given delivery service to caches, cache groups, types, and total.
-// TODO figure a way to associate this type with StatHTTP, with which its members correspond.
-type LastDSStat struct {
-	Caches      map[enum.CacheName]LastStatsData
-	CacheGroups map[enum.CacheGroupName]LastStatsData
-	Type        map[enum.CacheType]LastStatsData
-	Total       LastStatsData
-}
-
-// Copy performs a deep copy of this LastDSStat object.
-func (a LastDSStat) Copy() LastDSStat {
-	b := LastDSStat{
-		CacheGroups: map[enum.CacheGroupName]LastStatsData{},
-		Type:        map[enum.CacheType]LastStatsData{},
-		Caches:      map[enum.CacheName]LastStatsData{},
-		Total:       a.Total,
-	}
-	for k, v := range a.CacheGroups {
-		b.CacheGroups[k] = v
-	}
-	for k, v := range a.Type {
-		b.Type[k] = v
-	}
-	for k, v := range a.Caches {
-		b.Caches[k] = v
-	}
-	return b
-}
-
-func newLastDSStat() LastDSStat {
-	return LastDSStat{
-		CacheGroups: map[enum.CacheGroupName]LastStatsData{},
-		Type:        map[enum.CacheType]LastStatsData{},
-		Caches:      map[enum.CacheName]LastStatsData{},
-	}
-}
-
-// LastStatsData contains the last stats and per-second calculations for bytes and status codes received from a cache.
-type LastStatsData struct {
-	Bytes     LastStatData
-	Status2xx LastStatData
-	Status3xx LastStatData
-	Status4xx LastStatData
-	Status5xx LastStatData
-}
-
-// Sum returns the Sum() of each member data with the given LastStatsData corresponding members
-func (a LastStatsData) Sum(b LastStatsData) LastStatsData {
-	return LastStatsData{
-		Bytes:     a.Bytes.Sum(b.Bytes),
-		Status2xx: a.Status2xx.Sum(b.Status2xx),
-		Status3xx: a.Status3xx.Sum(b.Status3xx),
-		Status4xx: a.Status4xx.Sum(b.Status4xx),
-		Status5xx: a.Status5xx.Sum(b.Status5xx),
-	}
-}
-
-// LastStatData contains the value, time it was received, and per-second calculation since the previous stat, for a stat from a cache.
-type LastStatData struct {
-	PerSec float64
-	Stat   int64
-	Time   time.Time
-}
-
-// Sum adds the PerSec and Stat of the given data to this object. Time is meaningless for the summed object, and is thus set to 0.
-func (a LastStatData) Sum(b LastStatData) LastStatData {
-	return LastStatData{
-		PerSec: a.PerSec + b.PerSec,
-		Stat:   a.Stat + b.Stat,
-	}
-}
-
-func addCommonData(s *StatsOld, c *StatCommon, deliveryService enum.DeliveryServiceName, t int64, filter Filter) *StatsOld {
-	add := func(name string, val interface{}) {
-		if filter.UseStat(name) {
-			s.DeliveryService[deliveryService][StatName(name)] = []StatOld{StatOld{Time: t, Value: val}}
-		}
-	}
-	add("caches-configured", fmt.Sprintf("%d", c.CachesConfiguredNum.Value))
-	add("caches-reporting", fmt.Sprintf("%d", len(c.CachesReporting)))
-	add("error-string", c.ErrorStr.Value)
-	add("status", c.StatusStr.Value)
-	add("isHealthy", fmt.Sprintf("%t", c.IsHealthy.Value))
-	add("isAvailable", fmt.Sprintf("%t", c.IsAvailable.Value))
-	add("caches-available", fmt.Sprintf("%d", c.CachesAvailableNum.Value))
-	add("disabledLocations", c.CachesDisabled)
-	return s
-}
-
-func addStatCacheStats(s *StatsOld, c StatCacheStats, deliveryService enum.DeliveryServiceName, prefix string, t int64, filter Filter) *StatsOld {
-	add := func(name, val string) {
-		if filter.UseStat(name) {
-			// This is for compatibility with the Traffic Monitor 1.0 API.
-			// TODO abstract this? Or deprecate and remove it?
-			if name == "isAvailable" || name == "error-string" {
-				s.DeliveryService[deliveryService][StatName("location."+prefix+name)] = []StatOld{StatOld{Time: t, Value: val}}
-			} else {
-				s.DeliveryService[deliveryService][StatName(prefix+name)] = []StatOld{StatOld{Time: t, Value: val}}
-			}
-		}
-	}
-	add("out_bytes", strconv.Itoa(int(c.OutBytes.Value)))
-	add("isAvailable", fmt.Sprintf("%t", c.IsAvailable.Value))
-	add("status_5xx", strconv.Itoa(int(c.Status5xx.Value)))
-	add("status_4xx", strconv.Itoa(int(c.Status4xx.Value)))
-	add("status_3xx", strconv.Itoa(int(c.Status3xx.Value)))
-	add("status_2xx", strconv.Itoa(int(c.Status2xx.Value)))
-	add("in_bytes", strconv.Itoa(int(c.InBytes.Value)))
-	add("kbps", strconv.Itoa(int(c.Kbps.Value)))
-	add("tps_5xx", fmt.Sprintf("%f", c.Tps5xx.Value))
-	add("tps_4xx", fmt.Sprintf("%f", c.Tps4xx.Value))
-	add("tps_3xx", fmt.Sprintf("%f", c.Tps3xx.Value))
-	add("tps_2xx", fmt.Sprintf("%f", c.Tps2xx.Value))
-	add("error-string", c.ErrorString.Value)
-	add("tps_total", fmt.Sprintf("%f", c.TpsTotal.Value))
-	return s
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/enum/enum.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/enum/enum.go b/traffic_monitor/experimental/traffic_monitor/enum/enum.go
deleted file mode 100644
index 45546bd..0000000
--- a/traffic_monitor/experimental/traffic_monitor/enum/enum.go
+++ /dev/null
@@ -1,181 +0,0 @@
-// Package enum contains enumerations and strongly typed names.
-//
-// These enums should be treated as enumerables, and MUST NOT be cast as anything else (integer, strings, etc). Enums MUST NOT be compared to strings or integers via casting. Enumerable data SHOULD be stored as the enumeration, not as a string or number. The *only* reason they are internally represented as strings, is to make them implicitly serialize to human-readable JSON. They should not be treated as strings. Casting or storing strings or numbers defeats the purpose of enum safety and conveniences.
-//
-// When storing enumumerable data in memory, it SHOULD be converted to and stored as an enum via the corresponding `FromString` function, checked whether the conversion failed and Invalid values handled, and valid data stored as the enum. This guarantees stored data is valid, and catches invalid input as soon as possible.
-//
-// When adding new enum types, enums should be internally stored as strings, so they implicitly serialize as human-readable JSON, unless the performance or memory of integers is necessary (it almost certainly isn't). Enums should always have the "invalid" value as the empty string (or 0), so default-initialized enums are invalid.
-// Enums should always have a FromString() conversion function, to convert input data to enums. Conversion functions should usually be case-insensitive, and may ignore underscores or hyphens, depending on the use case.
-//
-package enum
-
-/*
- * 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 (
-	"strings"
-)
-
-// TrafficMonitorName is the hostname of a Traffic Monitor peer.
-type TrafficMonitorName string
-
-// CacheName is the hostname of a CDN cache.
-type CacheName string
-
-// CacheGroupName is the name of a CDN cachegroup.
-type CacheGroupName string
-
-// DeliveryServiceName is the name of a CDN delivery service.
-type DeliveryServiceName string
-
-// CacheType is the type (or tier) of a CDN cache.
-type CacheType string
-
-const (
-	// CacheTypeEdge represents an edge cache.
-	CacheTypeEdge = CacheType("EDGE")
-	// CacheTypeMid represents a mid cache.
-	CacheTypeMid = CacheType("MID")
-	// CacheTypeInvalid represents an cache type enumeration. Note this is the default construction for a CacheType.
-	CacheTypeInvalid = CacheType("")
-)
-
-func (c CacheName) String() string {
-	return string(c)
-}
-
-func (t TrafficMonitorName) String() string {
-	return string(t)
-}
-
-func (d DeliveryServiceName) String() string {
-	return string(d)
-}
-
-// String returns a string representation of this cache type.
-func (t CacheType) String() string {
-	switch t {
-	case CacheTypeEdge:
-		return "EDGE"
-	case CacheTypeMid:
-		return "MID"
-	default:
-		return "INVALIDCACHETYPE"
-	}
-}
-
-// CacheTypeFromString returns a cache type object from its string representation, or CacheTypeInvalid if the string is not a valid type.
-func CacheTypeFromString(s string) CacheType {
-	s = strings.ToLower(s)
-	if strings.HasPrefix(s, "edge") {
-		return CacheTypeEdge
-	}
-	if strings.HasPrefix(s, "mid") {
-		return CacheTypeMid
-	}
-	return CacheTypeInvalid
-}
-
-// DSType is the Delivery Service type. HTTP, DNS, etc.
-type DSType string
-
-const (
-	// DSTypeHTTP represents an HTTP delivery service
-	DSTypeHTTP = DSType("http")
-	// DSTypeDNS represents a DNS delivery service
-	DSTypeDNS = DSType("dns")
-	// DSTypeInvalid represents an invalid delivery service type enumeration. Note this is the default construction for a DSType.
-	DSTypeInvalid = DSType("")
-)
-
-// String returns a string representation of this delivery service type.
-func (t DSType) String() string {
-	switch t {
-	case DSTypeHTTP:
-		return "HTTP"
-	case DSTypeDNS:
-		return "DNS"
-	default:
-		return "INVALIDDSTYPE"
-	}
-}
-
-// DSTypeFromString returns a delivery service type object from its string representation, or DSTypeInvalid if the string is not a valid type.
-func DSTypeFromString(s string) DSType {
-	s = strings.ToLower(s)
-	switch s {
-	case "http":
-		return DSTypeHTTP
-	case "dns":
-		return DSTypeDNS
-	default:
-		return DSTypeInvalid
-	}
-}
-
-// CacheStatus represents the Traffic Server status set in Traffic Ops (online, offline, admin_down, reported). The string values of this type should match the Traffic Ops values.
-type CacheStatus string
-
-const (
-	// CacheStatusAdminDown represents a cache which has been administratively marked as down, but which should still appear in the CDN (Traffic Server, Traffic Monitor, Traffic Router).
-	CacheStatusAdminDown = CacheStatus("ADMIN_DOWN")
-	// CacheStatusOnline represents a cache which has been marked as Online in Traffic Ops, irrespective of monitoring. Traffic Monitor will always flag these caches as available.
-	CacheStatusOnline = CacheStatus("ONLINE")
-	// CacheStatusOffline represents a cache which has been marked as Offline in Traffic Ops. These caches will not be returned in any endpoint, and Traffic Monitor acts like they don't exist.
-	CacheStatusOffline = CacheStatus("OFFLINE")
-	// CacheStatusReported represents a cache which has been marked as Reported in Traffic Ops. These caches are polled for health and returned in endpoints as available or unavailable based on bandwidth, response time, and other factors. The vast majority of caches should be Reported.
-	CacheStatusReported = CacheStatus("REPORTED")
-	// CacheStatusInvalid represents an invalid status enumeration.
-	CacheStatusInvalid = CacheStatus("")
-)
-
-// String returns a string representation of this cache status
-func (t CacheStatus) String() string {
-	switch t {
-	case CacheStatusAdminDown:
-		fallthrough
-	case CacheStatusOnline:
-		fallthrough
-	case CacheStatusOffline:
-		fallthrough
-	case CacheStatusReported:
-		return string(t)
-	default:
-		return "INVALIDCACHESTATUS"
-	}
-}
-
-// CacheStatusFromString returns a CacheStatus from its string representation, or CacheStatusInvalid if the string is not a valid type.
-func CacheStatusFromString(s string) CacheStatus {
-	s = strings.ToLower(s)
-	switch s {
-	case "admin_down":
-		fallthrough
-	case "admindown":
-		return CacheStatusAdminDown
-	case "offline":
-		return CacheStatusOffline
-	case "online":
-		return CacheStatusOnline
-	case "reported":
-		return CacheStatusReported
-	default:
-		return CacheStatusInvalid
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/health/cache.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/health/cache.go b/traffic_monitor/experimental/traffic_monitor/health/cache.go
deleted file mode 100644
index 323d8e0..0000000
--- a/traffic_monitor/experimental/traffic_monitor/health/cache.go
+++ /dev/null
@@ -1,272 +0,0 @@
-package health
-
-/*
- * 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"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/util"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/cache"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/enum"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/peer"
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/threadsafe"
-	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/traffic_monitor/trafficopsdata"
-	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
-)
-
-// GetVitals Gets the vitals to decide health on in the right format
-func GetVitals(newResult *cache.Result, prevResult *cache.Result, mc *to.TrafficMonitorConfigMap) {
-	if newResult.Error != nil {
-		log.Errorf("cache_health.GetVitals() called with an errored Result!")
-		return
-	}
-	// proc.loadavg -- we're using the 1 minute average (!?)
-	// value looks like: "0.20 0.07 0.07 1/967 29536" (without the quotes)
-	loadAverages := strings.Fields(newResult.Astats.System.ProcLoadavg)
-	if len(loadAverages) > 0 {
-		oneMinAvg, err := strconv.ParseFloat(loadAverages[0], 64)
-		if err != nil {
-			setErr(newResult, fmt.Errorf("Error converting load average string '%s': %v", newResult.Astats.System.ProcLoadavg, err))
-			return
-		}
-		newResult.Vitals.LoadAvg = oneMinAvg
-	} else {
-		setErr(newResult, fmt.Errorf("Can't make sense of '%s' as a load average for %s", newResult.Astats.System.ProcLoadavg, newResult.ID))
-		return
-	}
-
-	// proc.net.dev -- need to compare to prevSample
-	// value looks like
-	// "bond0:8495786321839 31960528603    0    0    0     0          0   2349716 143283576747316 101104535041    0    0    0     0       0          0"
-	// (without the quotes)
-	parts := strings.Split(newResult.Astats.System.ProcNetDev, ":")
-	if len(parts) > 1 {
-		numbers := strings.Fields(parts[1])
-		var err error
-		newResult.Vitals.BytesOut, err = strconv.ParseInt(numbers[8], 10, 64)
-		if err != nil {
-			setErr(newResult, fmt.Errorf("Error converting BytesOut from procnetdev: %v", err))
-			return
-		}
-		newResult.Vitals.BytesIn, err = strconv.ParseInt(numbers[0], 10, 64)
-		if err != nil {
-			setErr(newResult, fmt.Errorf("Error converting BytesIn from procnetdev: %v", err))
-			return
-		}
-		if prevResult != nil && prevResult.Vitals.BytesOut != 0 {
-			elapsedTimeInSecs := float64(newResult.Time.UnixNano()-prevResult.Time.UnixNano()) / 1000000000
-			newResult.Vitals.KbpsOut = int64(float64(((newResult.Vitals.BytesOut - prevResult.Vitals.BytesOut) * 8 / 1000)) / elapsedTimeInSecs)
-		} else {
-			// log.Infoln("prevResult == nil for id " + newResult.Id + ". Hope we're just starting up?")
-		}
-	} else {
-		setErr(newResult, fmt.Errorf("Error parsing procnetdev: no fields found"))
-		return
-	}
-
-	// inf.speed -- value looks like "10000" (without the quotes) so it is in Mbps.
-	// TODO JvD: Should we really be running this code every second for every cache polled????? I don't think so.
-	interfaceBandwidth := newResult.Astats.System.InfSpeed
-	newResult.Vitals.MaxKbpsOut = int64(interfaceBandwidth) * 1000
-
-	// log.Infoln(newResult.Id, "BytesOut", newResult.Vitals.BytesOut, "BytesIn", newResult.Vitals.BytesIn, "Kbps", newResult.Vitals.KbpsOut, "max", newResult.Vitals.MaxKbpsOut)
-}
-
-// EvalCache returns whether the given cache should be marked available, a string describing why, and which stat exceeded a threshold. The `stats` may be nil, for pollers which don't poll stats.
-// The availability of EvalCache MAY NOT be used to directly set the cache's local availability, because the threshold stats may not be part of the poller which produced the result. Rather, if the cache was previously unavailable from a threshold, it must be verified that threshold stat is in the results before setting the cache to available.
-// TODO change to return a `cache.AvailableStatus`
-func EvalCache(result cache.ResultInfo, resultStats cache.ResultStatValHistory, mc *to.TrafficMonitorConfigMap) (bool, string, string) {
-	serverInfo, ok := mc.TrafficServer[string(result.ID)]
-	if !ok {
-		log.Errorf("Cache %v missing from from Traffic Ops Monitor Config - treating as OFFLINE\n", result.ID)
-		return false, "ERROR - server missing in Traffic Ops monitor config", ""
-	}
-	serverProfile, ok := mc.Profile[serverInfo.Profile]
-	if !ok {
-		log.Errorf("Cache %v profile %v missing from from Traffic Ops Monitor Config - treating as OFFLINE\n", result.ID, serverInfo.Profile)
-		return false, "ERROR - server profile missing in Traffic Ops monitor config", ""
-	}
-
-	status := enum.CacheStatusFromString(serverInfo.Status)
-	if status == enum.CacheStatusInvalid {
-		log.Errorf("Cache %v got invalid status from Traffic Ops '%v' - treating as Reported\n", result.ID, serverInfo.Status)
-	}
-
-	availability := "available"
-	if !result.Available {
-		availability = "unavailable"
-	}
-
-	switch {
-	case status == enum.CacheStatusInvalid:
-		log.Errorf("Cache %v got invalid status from Traffic Ops '%v' - treating as OFFLINE\n", result.ID, serverInfo.Status)
-		return false, eventDesc(status, availability+"; invalid status"), ""
-	case status == enum.CacheStatusAdminDown:
-		return false, eventDesc(status, availability), ""
-	case status == enum.CacheStatusOffline:
-		log.Errorf("Cache %v set to offline, but still polled\n", result.ID)
-		return false, eventDesc(status, availability), ""
-	case status == enum.CacheStatusOnline:
-		return true, eventDesc(status, availability), ""
-	case result.Error != nil:
-		return false, eventDesc(status, fmt.Sprintf("%v", result.Error)), ""
-	}
-
-	computedStats := cache.ComputedStats()
-
-	for stat, threshold := range serverProfile.Parameters.Thresholds {
-		resultStat := interface{}(nil)
-		if computedStatF, ok := computedStats[stat]; ok {
-			dummyCombinedstate := peer.IsAvailable{} // the only stats which use combinedState are things like isAvailable, which don't make sense to ever be thresholds.
-			resultStat = computedStatF(result, serverInfo, serverProfile, dummyCombinedstate)
-		} else {
-			if resultStats == nil {
-				continue
-			}
-			resultStatHistory, ok := resultStats[stat]
-			if !ok {
-				continue
-			}
-			if len(resultStatHistory) < 1 {
-				continue
-			}
-			resultStat = resultStatHistory[0].Val
-		}
-
-		resultStatNum, ok := util.ToNumeric(resultStat)
-		if !ok {
-			log.Errorf("health.EvalCache threshold stat %s was not a number: %v", stat, resultStat)
-			continue
-		}
-
-		if !inThreshold(threshold, resultStatNum) {
-			return false, eventDesc(status, exceedsThresholdMsg(stat, threshold, resultStatNum)), stat
-		}
-	}
-
-	return result.Available, eventDesc(status, availability), ""
-}
-
-// CalcAvailability calculates the availability of the cache, from the given result. Availability is stored in `localCacheStatus` and `localStates`, and if the status changed an event is added to `events`. statResultHistory may be nil, for pollers which don't poll stats.
-// TODO add enum for poller names?
-func CalcAvailability(results []cache.Result, pollerName string, statResultHistory cache.ResultStatHistory, mc to.TrafficMonitorConfigMap, toData todata.TOData, localCacheStatusThreadsafe threadsafe.CacheAvailableStatus, localStates peer.CRStatesThreadsafe, events ThreadsafeEvents) {
-	localCacheStatuses := localCacheStatusThreadsafe.Get().Copy()
-	for _, result := range results {
-		statResults := cache.ResultStatValHistory(nil)
-		if statResultHistory != nil {
-			statResults = statResultHistory[result.ID]
-		}
-
-		isAvailable, whyAvailable, unavailableStat := EvalCache(cache.ToInfo(result), statResults, &mc)
-
-		// if the cache is now Available, and was previously unavailable due to a threshold, make sure this poller contains the stat which exceeded the threshold.
-		if previousStatus, hasPreviousStatus := localCacheStatuses[result.ID]; isAvailable && hasPreviousStatus && !previousStatus.Available && previousStatus.UnavailableStat != "" {
-			if !result.HasStat(previousStatus.UnavailableStat) {
-				return
-			}
-		}
-		localCacheStatuses[result.ID] = cache.AvailableStatus{
-			Available:       isAvailable,
-			Status:          mc.TrafficServer[string(result.ID)].Status,
-			Why:             whyAvailable,
-			UnavailableStat: unavailableStat,
-			Poller:          pollerName,
-		} // TODO move within localStates?
-
-		if available, ok := localStates.GetCache(result.ID); !ok || available.IsAvailable != isAvailable {
-			log.Infof("Changing state for %s was: %t now: %t because %s poller: %v error: %v", result.ID, available.IsAvailable, isAvailable, whyAvailable, pollerName, result.Error)
-			events.Add(Event{Time: time.Now(), Description: whyAvailable + " (" + pollerName + ")", Name: string(result.ID), Hostname: string(result.ID), Type: toData.ServerTypes[result.ID].String(), Available: isAvailable})
-		}
-
-		localStates.SetCache(result.ID, peer.IsAvailable{IsAvailable: isAvailable})
-	}
-	calculateDeliveryServiceState(toData.DeliveryServiceServers, localStates)
-	localCacheStatusThreadsafe.Set(localCacheStatuses)
-}
-
-func setErr(newResult *cache.Result, err error) {
-	newResult.Error = err
-	newResult.Available = false
-}
-
-// ExceedsThresholdMsg returns a human-readable message for why the given value exceeds the threshold. It does NOT check whether the value actually exceeds the threshold; call `InThreshold` to check first.
-func exceedsThresholdMsg(stat string, threshold to.HealthThreshold, val float64) string {
-	switch threshold.Comparator {
-	case "=":
-		return fmt.Sprintf("%s not equal (%.2f != %.2f)", stat, val, threshold.Val)
-	case ">":
-		return fmt.Sprintf("%s too low (%.2f < %.2f)", stat, val, threshold.Val)
-	case "<":
-		return fmt.Sprintf("%s too high (%.2f > %.2f)", stat, val, threshold.Val)
-	case ">=":
-		return fmt.Sprintf("%s too low (%.2f <= %.2f)", stat, val, threshold.Val)
-	case "<=":
-		return fmt.Sprintf("%s too high (%.2f >= %.2f)", stat, val, threshold.Val)
-	default:
-		return fmt.Sprintf("ERROR: Invalid Threshold: %+v", threshold)
-	}
-}
-
-func inThreshold(threshold to.HealthThreshold, val float64) bool {
-	switch threshold.Comparator {
-	case "=":
-		return val == threshold.Val
-	case ">":
-		return val > threshold.Val
-	case "<":
-		return val < threshold.Val
-	case ">=":
-		return val >= threshold.Val
-	case "<=":
-		return val <= threshold.Val
-	default:
-		log.Errorf("Invalid Threshold: %+v", threshold)
-		return true // for safety, if a threshold somehow gets corrupted, don't start marking caches down.
-	}
-}
-
-func eventDesc(status enum.CacheStatus, message string) string {
-	return fmt.Sprintf("%s - %s", status, message)
-}
-
-// calculateDeliveryServiceState calculates the state of delivery services from the new cache state data `cacheState` and the CRConfig data `deliveryServiceServers` and puts the calculated state in the outparam `deliveryServiceStates`
-func calculateDeliveryServiceState(deliveryServiceServers map[enum.DeliveryServiceName][]enum.CacheName, states peer.CRStatesThreadsafe) {
-	deliveryServices := states.GetDeliveryServices()
-	for deliveryServiceName, deliveryServiceState := range deliveryServices {
-		if _, ok := deliveryServiceServers[deliveryServiceName]; !ok {
-			// log.Errorf("CRConfig does not have delivery service %s, but traffic monitor poller does; skipping\n", deliveryServiceName)
-			continue
-		}
-		deliveryServiceState.IsAvailable = false
-		deliveryServiceState.DisabledLocations = []enum.CacheName{} // it's important this isn't nil, so it serialises to the JSON `[]` instead of `null`
-		for _, server := range deliveryServiceServers[deliveryServiceName] {
-			if available, _ := states.GetCache(server); available.IsAvailable {
-				deliveryServiceState.IsAvailable = true
-			} else {
-				deliveryServiceState.DisabledLocations = append(deliveryServiceState.DisabledLocations, server)
-			}
-		}
-		states.SetDeliveryService(deliveryServiceName, deliveryServiceState)
-	}
-}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor/experimental/traffic_monitor/health/event.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/health/event.go b/traffic_monitor/experimental/traffic_monitor/health/event.go
deleted file mode 100644
index 3150da4..0000000
--- a/traffic_monitor/experimental/traffic_monitor/health/event.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package health
-
-/*
- * 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 (
-	"sync"
-	"time"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental/common/log"
-)
-
-// Event represents an event change in aggregated data. For example, a cache being marked as unavailable.
-type Event struct {
-	Time        time.Time `json:"-"`
-	Index       uint64    `json:"index"`
-	Unix        int64     `json:"time"`
-	Description string    `json:"description"`
-	Name        string    `json:"name"`
-	Hostname    string    `json:"hostname"`
-	Type        string    `json:"type"`
-	Available   bool      `json:"isAvailable"`
-}
-
-// Events provides safe access for multiple goroutines readers and a single writer to a stored Events slice.
-type ThreadsafeEvents struct {
-	events    *[]Event
-	m         *sync.RWMutex
-	nextIndex *uint64
-	max       uint64
-}
-
-func copyEvents(a []Event) []Event {
-	b := make([]Event, len(a), len(a))
-	copy(b, a)
-	return b
-}
-
-// NewEvents creates a new single-writer-multiple-reader Threadsafe object
-func NewThreadsafeEvents(maxEvents uint64) ThreadsafeEvents {
-	i := uint64(0)
-	return ThreadsafeEvents{m: &sync.RWMutex{}, events: &[]Event{}, nextIndex: &i, max: maxEvents}
-}
-
-// Get returns the internal slice of Events for reading. This MUST NOT be modified. If modification is necessary, copy the slice.
-func (o *ThreadsafeEvents) Get() []Event {
-	o.m.RLock()
-	defer o.m.RUnlock()
-	return *o.events
-}
-
-// Add adds the given event. This is threadsafe for one writer, multiple readers. This MUST NOT be called by multiple threads, as it non-atomically fetches and adds.
-func (o *ThreadsafeEvents) Add(e Event) {
-	// host="hostname", type=EDGE, available=true, msg="REPORTED - available"
-	log.Eventf(e.Time, "host=\"%s\", type=%s, available=%t, msg=\"%s\"", e.Hostname, e.Type, e.Available, e.Description)
-	o.m.Lock() // TODO test removing
-	events := copyEvents(*o.events)
-	e.Index = *o.nextIndex
-	events = append([]Event{e}, events...)
-	if len(events) > int(o.max) {
-		events = (events)[:o.max-1]
-	}
-	// o.m.Lock()
-	*o.events = events
-	*o.nextIndex++
-	o.m.Unlock()
-}


[06/19] incubator-trafficcontrol git commit: Move TM2 to trafficcontrol/traffic_monitor_golang

Posted by ne...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/manager/peer.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/manager/peer.go b/traffic_monitor_golang/traffic_monitor/manager/peer.go
new file mode 100644
index 0000000..e103585
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/manager/peer.go
@@ -0,0 +1,165 @@
+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 (
+	"fmt"
+	"sort"
+	"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/config"
+	"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"
+	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
+)
+
+// StartPeerManager listens for peer results, and when it gets one, it adds it to the peerStates list, and optimistically combines the good results into combinedStates
+func StartPeerManager(
+	peerChan <-chan peer.Result,
+	localStates peer.CRStatesThreadsafe,
+	peerStates peer.CRStatesPeersThreadsafe,
+	events health.ThreadsafeEvents,
+	peerOptimistic bool,
+	toData todata.TODataThreadsafe,
+	cfg config.Config,
+) (peer.CRStatesThreadsafe, health.ThreadsafeEvents) {
+	combinedStates := peer.NewCRStatesThreadsafe()
+	overrideMap := map[enum.CacheName]bool{}
+
+	go func() {
+		for peerResult := range peerChan {
+			comparePeerState(events, peerResult, peerStates)
+			peerStates.Set(peerResult)
+			combineCrStates(events, peerOptimistic, peerStates, localStates.Get(), combinedStates, overrideMap, toData)
+			peerResult.PollFinished <- peerResult.PollID
+		}
+	}()
+	return combinedStates, events
+}
+
+func comparePeerState(events health.ThreadsafeEvents, result peer.Result, peerStates peer.CRStatesPeersThreadsafe) {
+	if result.Available != peerStates.GetPeerAvailability(result.ID) {
+		events.Add(health.Event{Time: result.Time, Unix: result.Time.Unix(), Description: util.JoinErrorsString(result.Errors), Name: result.ID.String(), Hostname: result.ID.String(), Type: "Peer", Available: result.Available})
+	}
+}
+
+// TODO JvD: add deliveryservice stuff
+func combineCrStates(events health.ThreadsafeEvents, peerOptimistic bool, peerStates peer.CRStatesPeersThreadsafe, localStates peer.Crstates, combinedStates peer.CRStatesThreadsafe, overrideMap map[enum.CacheName]bool, toData todata.TODataThreadsafe) {
+	toDataCopy := toData.Get()
+
+	for cacheName, localCacheState := range localStates.Caches { // localStates gets pruned when servers are disabled, it's the source of truth
+		var overrideCondition string
+		available := false
+		override := overrideMap[cacheName]
+
+		if localCacheState.IsAvailable {
+			available = true // we don't care about the peers, we got a "good one", and we're optimistic
+
+			if override {
+				overrideCondition = "cleared; healthy locally"
+				overrideMap[cacheName] = false
+			}
+		} else if peerOptimistic {
+			if !peerStates.HasAvailablePeers() {
+				if override {
+					overrideCondition = "irrelevant; no peers online"
+					overrideMap[cacheName] = false
+				}
+			} else {
+				onlineOnPeers := make([]string, 0)
+
+				for peer, peerCrStates := range peerStates.GetCrstates() {
+					if peerStates.GetPeerAvailability(peer) {
+						if peerCrStates.Caches[cacheName].IsAvailable {
+							onlineOnPeers = append(onlineOnPeers, peer.String())
+						}
+					}
+				}
+
+				if len(onlineOnPeers) > 0 {
+					available = true
+
+					if !override {
+						overrideCondition = fmt.Sprintf("detected; healthy on (at least) %s", strings.Join(onlineOnPeers, ", "))
+						overrideMap[cacheName] = true
+					}
+				} else {
+					if override {
+						overrideCondition = "irrelevant; not online on any peers"
+						overrideMap[cacheName] = false
+					}
+				}
+			}
+		}
+
+		if overrideCondition != "" {
+			events.Add(health.Event{Time: time.Now(), Unix: time.Now().Unix(), Description: fmt.Sprintf("Health protocol override condition %s", overrideCondition), Name: cacheName.String(), Hostname: cacheName.String(), Type: toDataCopy.ServerTypes[cacheName].String(), Available: available})
+		}
+
+		combinedStates.SetCache(cacheName, peer.IsAvailable{IsAvailable: available})
+	}
+
+	for deliveryServiceName, localDeliveryService := range localStates.Deliveryservice {
+		deliveryService := peer.Deliveryservice{IsAvailable: false, DisabledLocations: []enum.CacheName{}} // important to initialize DisabledLocations, so JSON is `[]` not `null`
+		if localDeliveryService.IsAvailable {
+			deliveryService.IsAvailable = true
+		}
+		deliveryService.DisabledLocations = localDeliveryService.DisabledLocations
+
+		for peerName, iPeerStates := range peerStates.GetCrstates() {
+			peerDeliveryService, ok := iPeerStates.Deliveryservice[deliveryServiceName]
+			if !ok {
+				log.Warnf("local delivery service %s not found in peer %s\n", deliveryServiceName, peerName)
+				continue
+			}
+			if peerDeliveryService.IsAvailable {
+				deliveryService.IsAvailable = true
+			}
+			deliveryService.DisabledLocations = intersection(deliveryService.DisabledLocations, peerDeliveryService.DisabledLocations)
+		}
+		combinedStates.SetDeliveryService(deliveryServiceName, deliveryService)
+	}
+}
+
+// CacheNameSlice is a slice of cache names, which fulfills the `sort.Interface` interface.
+type CacheNameSlice []enum.CacheName
+
+func (p CacheNameSlice) Len() int           { return len(p) }
+func (p CacheNameSlice) Less(i, j int) bool { return p[i] < p[j] }
+func (p CacheNameSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+
+// intersection returns strings in both a and b.
+// Note this modifies a and b. Specifically, it sorts them. If that isn't acceptable, pass copies of your real data.
+func intersection(a []enum.CacheName, b []enum.CacheName) []enum.CacheName {
+	sort.Sort(CacheNameSlice(a))
+	sort.Sort(CacheNameSlice(b))
+	c := []enum.CacheName{} // important to initialize, so JSON is `[]` not `null`
+	for _, s := range a {
+		i := sort.Search(len(b), func(i int) bool { return b[i] >= s })
+		if i < len(b) && b[i] == s {
+			c = append(c, s)
+		}
+	}
+	return c
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/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
new file mode 100644
index 0000000..bb03397
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/manager/stat.go
@@ -0,0 +1,219 @@
+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 (
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/cache"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/config"
+	ds "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/deliveryservice"
+	"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/threadsafe"
+	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
+	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
+)
+
+func pruneHistory(history []cache.Result, limit uint64) []cache.Result {
+	if uint64(len(history)) > limit {
+		history = history[:limit-1]
+	}
+	return history
+}
+
+func getNewCaches(localStates peer.CRStatesThreadsafe, monitorConfigTS TrafficMonitorConfigMapThreadsafe) map[enum.CacheName]struct{} {
+	monitorConfig := monitorConfigTS.Get()
+	caches := map[enum.CacheName]struct{}{}
+	for cacheName := range localStates.GetCaches() {
+		// ONLINE and OFFLINE caches are not polled.
+		// TODO add a function IsPolled() which can be called by this and the monitorConfig func which sets the polling, to prevent updating in one place breaking the other.
+		if ts, ok := monitorConfig.TrafficServer[string(cacheName)]; !ok || ts.Status == "ONLINE" || ts.Status == "OFFLINE" {
+			continue
+		}
+		caches[cacheName] = struct{}{}
+	}
+	return caches
+}
+
+// StartStatHistoryManager fetches the full statistics data from ATS Astats. This includes everything needed for all calculations, such as Delivery Services. This is expensive, though, and may be hard on ATS, so it should poll less often.
+// For a fast 'is it alive' poll, use the Health Result Manager poll.
+// Returns the stat history, the duration between the stat poll for each cache, the last Kbps data, the calculated Delivery Service stats, and the unpolled caches list.
+func StartStatHistoryManager(
+	cacheStatChan <-chan cache.Result,
+	localStates peer.CRStatesThreadsafe,
+	combinedStates peer.CRStatesThreadsafe,
+	toData todata.TODataThreadsafe,
+	cachesChanged <-chan struct{},
+	errorCount threadsafe.Uint,
+	cfg config.Config,
+	monitorConfig TrafficMonitorConfigMapThreadsafe,
+	events health.ThreadsafeEvents,
+) (threadsafe.ResultInfoHistory, threadsafe.ResultStatHistory, threadsafe.CacheKbpses, DurationMapThreadsafe, threadsafe.LastStats, threadsafe.DSStatsReader, threadsafe.UnpolledCaches, threadsafe.CacheAvailableStatus) {
+	statInfoHistory := threadsafe.NewResultInfoHistory()
+	statResultHistory := threadsafe.NewResultStatHistory()
+	statMaxKbpses := threadsafe.NewCacheKbpses()
+	lastStatDurations := NewDurationMapThreadsafe()
+	lastStatEndTimes := map[enum.CacheName]time.Time{}
+	lastStats := threadsafe.NewLastStats()
+	dsStats := threadsafe.NewDSStats()
+	unpolledCaches := threadsafe.NewUnpolledCaches()
+	tickInterval := cfg.StatFlushInterval
+	localCacheStatus := threadsafe.NewCacheAvailableStatus()
+
+	precomputedData := map[enum.CacheName]cache.PrecomputedData{}
+	lastResults := map[enum.CacheName]cache.Result{}
+
+	process := func(results []cache.Result) {
+		processStatResults(results, statInfoHistory, statResultHistory, statMaxKbpses, combinedStates.Get(), lastStats, toData.Get(), errorCount, dsStats, lastStatEndTimes, lastStatDurations, unpolledCaches, monitorConfig.Get(), precomputedData, lastResults, localStates, events, localCacheStatus)
+	}
+
+	go func() {
+		var ticker *time.Ticker
+		<-cachesChanged // wait for the signal that localStates have been set
+		unpolledCaches.SetNewCaches(getNewCaches(localStates, monitorConfig))
+
+		for {
+			var results []cache.Result
+			results = append(results, <-cacheStatChan)
+			if ticker != nil {
+				ticker.Stop()
+			}
+			ticker = time.NewTicker(tickInterval)
+		innerLoop:
+			for {
+				select {
+				case <-cachesChanged:
+					unpolledCaches.SetNewCaches(getNewCaches(localStates, monitorConfig))
+				case <-ticker.C:
+					log.Infof("StatHistoryManager flushing queued results\n")
+					process(results)
+					break innerLoop
+				default:
+					select {
+					case r := <-cacheStatChan:
+						results = append(results, r)
+					default:
+						process(results)
+						break innerLoop
+					}
+				}
+			}
+		}
+	}()
+	return statInfoHistory, statResultHistory, statMaxKbpses, lastStatDurations, lastStats, &dsStats, unpolledCaches, localCacheStatus
+}
+
+// processStatResults processes the given results, creating and setting DSStats, LastStats, and other stats. Note this is NOT threadsafe, and MUST NOT be called from multiple threads.
+func processStatResults(
+	results []cache.Result,
+	statInfoHistoryThreadsafe threadsafe.ResultInfoHistory,
+	statResultHistoryThreadsafe threadsafe.ResultStatHistory,
+	statMaxKbpsesThreadsafe threadsafe.CacheKbpses,
+	combinedStates peer.Crstates,
+	lastStats threadsafe.LastStats,
+	toData todata.TOData,
+	errorCount threadsafe.Uint,
+	dsStats threadsafe.DSStats,
+	lastStatEndTimes map[enum.CacheName]time.Time,
+	lastStatDurationsThreadsafe DurationMapThreadsafe,
+	unpolledCaches threadsafe.UnpolledCaches,
+	mc to.TrafficMonitorConfigMap,
+	precomputedData map[enum.CacheName]cache.PrecomputedData,
+	lastResults map[enum.CacheName]cache.Result,
+	localStates peer.CRStatesThreadsafe,
+	events health.ThreadsafeEvents,
+	localCacheStatusThreadsafe threadsafe.CacheAvailableStatus,
+) {
+	if len(results) == 0 {
+		return
+	}
+	defer func() {
+		for _, r := range results {
+			// log.Debugf("poll %v %v statfinish\n", result.PollID, endTime)
+			r.PollFinished <- r.PollID
+		}
+	}()
+
+	// setting the statHistory could be put in a goroutine concurrent with `ds.CreateStats`, if it were slow
+	statInfoHistory := statInfoHistoryThreadsafe.Get().Copy()
+	statResultHistory := statResultHistoryThreadsafe.Get().Copy()
+	statMaxKbpses := statMaxKbpsesThreadsafe.Get().Copy()
+
+	for i, result := range results {
+		maxStats := uint64(mc.Profile[mc.TrafficServer[string(result.ID)].Profile].Parameters.HistoryCount)
+		if maxStats < 1 {
+			log.Infof("processStatResults got history count %v for %v, setting to 1\n", maxStats, result.ID)
+			maxStats = 1
+		}
+
+		// TODO determine if we want to add results with errors, or just print the errors now and don't add them.
+		if lastResult, ok := lastResults[result.ID]; ok && result.Error == nil {
+			health.GetVitals(&result, &lastResult, &mc) // TODO precompute
+			if result.Error == nil {
+				results[i] = result
+			} else {
+				log.Errorf("stat poll getting vitals for %v: %v\n", result.ID, result.Error)
+			}
+		}
+		statInfoHistory.Add(result, maxStats)
+		statResultHistory.Add(result, maxStats)
+		// Don't add errored maxes or precomputed DSStats
+		if result.Error == nil {
+			// max and precomputed always contain the latest result from each cache
+			statMaxKbpses.AddMax(result)
+			// if we failed to compute the OutBytes, keep the outbytes of the last result.
+			if result.PrecomputedData.OutBytes == 0 {
+				result.PrecomputedData.OutBytes = precomputedData[result.ID].OutBytes
+			}
+			precomputedData[result.ID] = result.PrecomputedData
+
+		}
+		lastResults[result.ID] = result
+	}
+	statInfoHistoryThreadsafe.Set(statInfoHistory)
+	statResultHistoryThreadsafe.Set(statResultHistory)
+	statMaxKbpsesThreadsafe.Set(statMaxKbpses)
+
+	newDsStats, newLastStats, err := ds.CreateStats(precomputedData, toData, combinedStates, lastStats.Get().Copy(), time.Now(), mc, events)
+	if err != nil {
+		errorCount.Inc()
+		log.Errorf("getting deliveryservice: %v\n", err)
+	} else {
+		dsStats.Set(newDsStats)
+		lastStats.Set(newLastStats)
+	}
+
+	health.CalcAvailability(results, "stat", statResultHistory, mc, toData, localCacheStatusThreadsafe, localStates, events)
+
+	endTime := time.Now()
+	lastStatDurations := lastStatDurationsThreadsafe.Get().Copy()
+	for _, result := range results {
+		if lastStatStart, ok := lastStatEndTimes[result.ID]; ok {
+			d := time.Since(lastStatStart)
+			lastStatDurations[result.ID] = d
+		}
+		lastStatEndTimes[result.ID] = endTime
+	}
+	lastStatDurationsThreadsafe.Set(lastStatDurations)
+	unpolledCaches.SetPolled(results, lastStats.Get())
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/peer/crstates.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/peer/crstates.go b/traffic_monitor_golang/traffic_monitor/peer/crstates.go
new file mode 100644
index 0000000..62bb810
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/peer/crstates.go
@@ -0,0 +1,233 @@
+package peer
+
+/*
+ * 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"
+	"sync"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+)
+
+// Crstates includes availability data for caches and delivery services, as gathered and aggregated by this Traffic Monitor. It is designed to be served at an API endpoint primarily for Traffic Routers (Content Router) to consume.
+// TODO rename to `CRStates`
+type Crstates struct {
+	Caches          map[enum.CacheName]IsAvailable               `json:"caches"`
+	Deliveryservice map[enum.DeliveryServiceName]Deliveryservice `json:"deliveryServices"`
+}
+
+// NewCrstates creates a new CR states object, initializing pointer members.
+func NewCrstates() Crstates {
+	return Crstates{
+		Caches:          map[enum.CacheName]IsAvailable{},
+		Deliveryservice: map[enum.DeliveryServiceName]Deliveryservice{},
+	}
+}
+
+// Copy creates a deep copy of this object. It does not mutate, and is thus safe for multiple goroutines.
+func (a Crstates) Copy() Crstates {
+	b := NewCrstates()
+	for k, v := range a.Caches {
+		b.Caches[k] = v
+	}
+	for k, v := range a.Deliveryservice {
+		b.Deliveryservice[k] = v
+	}
+	return b
+}
+
+// CopyDeliveryservices creates a deep copy of the delivery service availability data.. It does not mutate, and is thus safe for multiple goroutines.
+func (a Crstates) CopyDeliveryservices() map[enum.DeliveryServiceName]Deliveryservice {
+	b := map[enum.DeliveryServiceName]Deliveryservice{}
+	for k, v := range a.Deliveryservice {
+		b[k] = v
+	}
+	return b
+}
+
+// CopyCaches creates a deep copy of the cache availability data.. It does not mutate, and is thus safe for multiple goroutines.
+func (a Crstates) CopyCaches() map[enum.CacheName]IsAvailable {
+	b := map[enum.CacheName]IsAvailable{}
+	for k, v := range a.Caches {
+		b[k] = v
+	}
+	return b
+}
+
+// IsAvailable contains whether the given cache or delivery service is available. It is designed for JSON serialization, namely in the Traffic Monitor 1.0 API.
+type IsAvailable struct {
+	IsAvailable bool `json:"isAvailable"`
+}
+
+// Deliveryservice contains data about the availability of a particular delivery service, and which caches in that delivery service have been marked as unavailable.
+type Deliveryservice struct {
+	DisabledLocations []enum.CacheName `json:"disabledLocations"`
+	IsAvailable       bool             `json:"isAvailable"`
+}
+
+// CrstatesUnMarshall takes bytes of a JSON string, and unmarshals them into a Crstates object.
+func CrstatesUnMarshall(body []byte) (Crstates, error) {
+	var crStates Crstates
+	err := json.Unmarshal(body, &crStates)
+	return crStates, err
+}
+
+// CrstatesMarshall serializes the given Crstates into bytes.
+func CrstatesMarshall(states Crstates) ([]byte, error) {
+	return json.Marshal(states)
+}
+
+// CRStatesThreadsafe provides safe access for multiple goroutines to read a single Crstates object, with a single goroutine writer.
+// This could be made lock-free, if the performance was necessary
+// TODO add separate locks for Caches and Deliveryservice maps?
+type CRStatesThreadsafe struct {
+	crStates *Crstates
+	m        *sync.RWMutex
+}
+
+// NewCRStatesThreadsafe creates a new CRStatesThreadsafe object safe for multiple goroutine readers and a single writer.
+func NewCRStatesThreadsafe() CRStatesThreadsafe {
+	crs := NewCrstates()
+	return CRStatesThreadsafe{m: &sync.RWMutex{}, crStates: &crs}
+}
+
+// Get returns the internal Crstates object for reading.
+func (t *CRStatesThreadsafe) Get() Crstates {
+	t.m.RLock()
+	defer t.m.RUnlock()
+	return t.crStates.Copy()
+}
+
+// GetDeliveryServices returns the internal Crstates delivery services map for reading.
+// TODO add GetCaches, GetDeliveryservices?
+func (t *CRStatesThreadsafe) GetDeliveryServices() map[enum.DeliveryServiceName]Deliveryservice {
+	t.m.RLock()
+	defer t.m.RUnlock()
+	return t.crStates.CopyDeliveryservices()
+}
+
+// GetCache returns the availability data of the given cache. This does not mutate, and is thus safe for multiple goroutines to call.
+func (t *CRStatesThreadsafe) GetCache(name enum.CacheName) (available IsAvailable, ok bool) {
+	t.m.RLock()
+	available, ok = t.crStates.Caches[name]
+	t.m.RUnlock()
+	return
+}
+
+// GetCaches returns the availability data of all caches. This does not mutate, and is thus safe for multiple goroutines to call.
+func (t *CRStatesThreadsafe) GetCaches() map[enum.CacheName]IsAvailable {
+	t.m.RLock()
+	defer t.m.RUnlock()
+	return t.crStates.CopyCaches()
+}
+
+// GetDeliveryService returns the availability data of the given delivery service. This does not mutate, and is thus safe for multiple goroutines to call.
+func (t *CRStatesThreadsafe) GetDeliveryService(name enum.DeliveryServiceName) (ds Deliveryservice, ok bool) {
+	t.m.RLock()
+	ds, ok = t.crStates.Deliveryservice[name]
+	t.m.RUnlock()
+	return
+}
+
+// SetCache sets the internal availability data for a particular cache.
+func (t *CRStatesThreadsafe) SetCache(cacheName enum.CacheName, available IsAvailable) {
+	t.m.Lock()
+	t.crStates.Caches[cacheName] = available
+	t.m.Unlock()
+}
+
+// DeleteCache deletes the given cache from the internal data.
+func (t *CRStatesThreadsafe) DeleteCache(name enum.CacheName) {
+	t.m.Lock()
+	delete(t.crStates.Caches, name)
+	t.m.Unlock()
+}
+
+// SetDeliveryService sets the availability data for the given delivery service.
+func (t *CRStatesThreadsafe) SetDeliveryService(name enum.DeliveryServiceName, ds Deliveryservice) {
+	t.m.Lock()
+	t.crStates.Deliveryservice[name] = ds
+	t.m.Unlock()
+}
+
+// DeleteDeliveryService deletes the given delivery service from the internal data. This MUST NOT be called by multiple goroutines.
+func (t *CRStatesThreadsafe) DeleteDeliveryService(name enum.DeliveryServiceName) {
+	t.m.Lock()
+	delete(t.crStates.Deliveryservice, name)
+	t.m.Unlock()
+}
+
+// CRStatesPeersThreadsafe provides safe access for multiple goroutines to read a map of Traffic Monitor peers to their returned Crstates, with a single goroutine writer.
+// This could be made lock-free, if the performance was necessary
+type CRStatesPeersThreadsafe struct {
+	crStates   map[enum.TrafficMonitorName]Crstates
+	peerStates map[enum.TrafficMonitorName]bool
+	m          *sync.RWMutex
+}
+
+// NewCRStatesPeersThreadsafe creates a new CRStatesPeers object safe for multiple goroutine readers and a single writer.
+func NewCRStatesPeersThreadsafe() CRStatesPeersThreadsafe {
+	return CRStatesPeersThreadsafe{m: &sync.RWMutex{}, crStates: map[enum.TrafficMonitorName]Crstates{}, peerStates: map[enum.TrafficMonitorName]bool{}}
+}
+
+// GetCrstates returns the internal Traffic Monitor peer Crstates data. This MUST NOT be modified.
+func (t *CRStatesPeersThreadsafe) GetCrstates() map[enum.TrafficMonitorName]Crstates {
+	t.m.RLock()
+	m := map[enum.TrafficMonitorName]Crstates{}
+	for k, v := range t.crStates {
+		m[k] = v.Copy()
+	}
+	t.m.RUnlock()
+	return m
+}
+
+// GetPeerAvailability returns the state of the given peer
+func (t *CRStatesPeersThreadsafe) GetPeerAvailability(peer enum.TrafficMonitorName) bool {
+	t.m.RLock()
+	availability := t.peerStates[peer]
+	t.m.RUnlock()
+	return availability
+}
+
+// HasAvailablePeers returns true if at least one peer is online
+func (t *CRStatesPeersThreadsafe) HasAvailablePeers() bool {
+	availablePeers := false
+
+	t.m.RLock()
+
+	for _, available := range t.peerStates {
+		if available {
+			availablePeers = true
+			break
+		}
+	}
+
+	t.m.RUnlock()
+
+	return availablePeers
+}
+
+// Set sets the internal Traffic Monitor peer state and Crstates data. This MUST NOT be called by multiple goroutines.
+func (t *CRStatesPeersThreadsafe) Set(result Result) {
+	t.m.Lock()
+	t.crStates[result.ID] = result.PeerStates
+	t.peerStates[result.ID] = result.Available
+	t.m.Unlock()
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/peer/crstates.json
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/peer/crstates.json b/traffic_monitor_golang/traffic_monitor/peer/crstates.json
new file mode 100644
index 0000000..d8ffe03
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/peer/crstates.json
@@ -0,0 +1,16 @@
+{
+  "caches": {
+    "ats-edge-cache-0": {"isAvailable": true},
+    "ats-edge-cache-1": {"isAvailable": false}
+  },
+  "deliveryServices": {
+    "delivery-service-0": {
+      "disabledLocations": [],
+      "isAvailable": true
+    },
+    "delivery-service-1": {
+      "disabledLocations": [],
+      "isAvailable": true
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/peer/peer.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/peer/peer.go b/traffic_monitor_golang/traffic_monitor/peer/peer.go
new file mode 100644
index 0000000..c6cac61
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/peer/peer.go
@@ -0,0 +1,79 @@
+package peer
+
+/*
+ * 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"
+	"io"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+)
+
+// Handler handles peer Traffic Monitor data, taking a raw reader, parsing the data, and passing a result object to the ResultChannel. This fulfills the common `Handler` interface.
+type Handler struct {
+	ResultChannel chan Result
+	Notify        int
+}
+
+// NewHandler returns a new peer Handler.
+func NewHandler() Handler {
+	return Handler{ResultChannel: make(chan Result)}
+}
+
+// Result contains the data parsed from polling a peer Traffic Monitor.
+type Result struct {
+	ID           enum.TrafficMonitorName
+	Available    bool
+	Errors       []error
+	PeerStates   Crstates
+	PollID       uint64
+	PollFinished chan<- uint64
+	Time         time.Time
+}
+
+// Handle handles a response from a polled Traffic Monitor peer, parsing the data and forwarding it to the ResultChannel.
+func (handler Handler) Handle(id string, r io.Reader, reqTime time.Duration, err error, pollID uint64, pollFinished chan<- uint64) {
+	result := Result{
+		ID:           enum.TrafficMonitorName(id),
+		Available:    false,
+		Errors:       []error{},
+		PollID:       pollID,
+		PollFinished: pollFinished,
+		Time:         time.Now(),
+	}
+
+	if err != nil {
+		result.Errors = append(result.Errors, err)
+	}
+
+	if r != nil {
+		dec := json.NewDecoder(r)
+		err = dec.Decode(&result.PeerStates)
+
+		if err == nil {
+			result.Available = true
+		} else {
+			result.Errors = append(result.Errors, err)
+		}
+	}
+
+	handler.ResultChannel <- result
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/peer/peer_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/peer/peer_test.go b/traffic_monitor_golang/traffic_monitor/peer/peer_test.go
new file mode 100644
index 0000000..627a825
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/peer/peer_test.go
@@ -0,0 +1,50 @@
+package peer
+
+/*
+ * 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"
+	"io/ioutil"
+	"testing"
+)
+
+func TestCrStates(t *testing.T) {
+	t.Log("Running Peer Tests")
+
+	text, err := ioutil.ReadFile("crstates.json")
+	if err != nil {
+		t.Log(err)
+	}
+	crStates, err := CrstatesUnMarshall(text)
+	if err != nil {
+		t.Log(err)
+	}
+	fmt.Println(len(crStates.Caches), "caches found")
+	for cacheName, crState := range crStates.Caches {
+		t.Logf("%v -> %v", cacheName, crState.IsAvailable)
+	}
+
+	fmt.Println(len(crStates.Deliveryservice), "deliveryservices found")
+	for dsName, deliveryService := range crStates.Deliveryservice {
+		t.Logf("%v -> %v (len:%v)", dsName, deliveryService.IsAvailable, len(deliveryService.DisabledLocations))
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/srvhttp/srvhttp.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/srvhttp/srvhttp.go b/traffic_monitor_golang/traffic_monitor/srvhttp/srvhttp.go
new file mode 100644
index 0000000..91bbb87
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/srvhttp/srvhttp.go
@@ -0,0 +1,164 @@
+package srvhttp
+
+/*
+ * 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"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"net/url"
+	"sync"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
+	"github.com/hydrogen18/stoppableListener"
+)
+
+// GetCommonAPIData calculates and returns API data common to most endpoints
+func GetCommonAPIData(params url.Values, t time.Time) CommonAPIData {
+	return CommonAPIData{
+		QueryParams: ParametersStr(params),
+		DateStr:     DateStr(t),
+	}
+}
+
+// CommonAPIData contains generic data common to most endpoints.
+type CommonAPIData struct {
+	QueryParams string `json:"pp"`
+	DateStr     string `json:"date"`
+}
+
+// Server is a re-runnable HTTP server. Server.Run() may be called repeatedly, and
+// each time the previous running server will be stopped, and the server will be
+// restarted with the new port address and data request channel.
+type Server struct {
+	stoppableListener          *stoppableListener.StoppableListener
+	stoppableListenerWaitGroup sync.WaitGroup
+}
+
+func (s *Server) registerEndpoints(sm *http.ServeMux, endpoints map[string]http.HandlerFunc, staticFileDir string) error {
+	handleRoot, err := s.handleRootFunc(staticFileDir)
+	if err != nil {
+		return fmt.Errorf("Error getting root endpoint: %v", err)
+	}
+	handleSortableJs, err := s.handleSortableFunc(staticFileDir)
+	if err != nil {
+		return fmt.Errorf("Error getting sortable endpoint: %v", err)
+	}
+
+	for path, f := range endpoints {
+		sm.HandleFunc(path, f)
+	}
+
+	sm.HandleFunc("/", handleRoot)
+	sm.HandleFunc("/sorttable.js", handleSortableJs)
+
+	return nil
+}
+
+// Run runs a new HTTP service at the given addr, making data requests to the given c.
+// Run may be called repeatedly, and each time, will shut down any existing service first.
+// Run is NOT threadsafe, and MUST NOT be called concurrently by multiple goroutines.
+func (s *Server) Run(endpoints map[string]http.HandlerFunc, addr string, readTimeout time.Duration, writeTimeout time.Duration, staticFileDir string) error {
+	if s.stoppableListener != nil {
+		log.Infof("Stopping Web Server\n")
+		s.stoppableListener.Stop()
+		s.stoppableListenerWaitGroup.Wait()
+	}
+	log.Infof("Starting Web Server\n")
+
+	var err error
+	var originalListener net.Listener
+	if originalListener, err = net.Listen("tcp", addr); err != nil {
+		return err
+	}
+	if s.stoppableListener, err = stoppableListener.New(originalListener); err != nil {
+		return err
+	}
+
+	sm := http.NewServeMux()
+	err = s.registerEndpoints(sm, endpoints, staticFileDir)
+	if err != nil {
+		return err
+	}
+	server := &http.Server{
+		Addr:           addr,
+		Handler:        sm,
+		ReadTimeout:    readTimeout,
+		WriteTimeout:   writeTimeout,
+		MaxHeaderBytes: 1 << 20,
+	}
+
+	s.stoppableListenerWaitGroup = sync.WaitGroup{}
+	s.stoppableListenerWaitGroup.Add(1)
+	go func() {
+		defer s.stoppableListenerWaitGroup.Done()
+		err := server.Serve(s.stoppableListener)
+		if err != nil {
+			log.Warnf("HTTP server stopped with error: %v\n", err)
+		}
+	}()
+
+	log.Infof("Web server listening on %s", addr)
+	return nil
+}
+
+// ParametersStr takes the URL query parameters, and returns a string as used by the Traffic Monitor 1.0 endpoints "pp" key.
+func ParametersStr(params url.Values) string {
+	pp := ""
+	for param, vals := range params {
+		for _, val := range vals {
+			pp += param + "=[" + val + "], "
+		}
+	}
+	if len(pp) > 2 {
+		pp = pp[:len(pp)-2]
+	}
+	return pp
+}
+
+//CommonAPIDataDataFormat is a common Date format for the API
+const CommonAPIDataDateFormat = "Mon Jan 02 15:04:05 UTC 2006"
+
+// DateStr returns the given time in the format expected by Traffic Monitor 1.0 API users
+func DateStr(t time.Time) string {
+	return t.UTC().Format(CommonAPIDataDateFormat)
+}
+
+func (s *Server) handleRootFunc(staticFileDir string) (http.HandlerFunc, error) {
+	return s.handleFile(staticFileDir + "/index.html")
+}
+
+func (s *Server) handleSortableFunc(staticFileDir string) (http.HandlerFunc, error) {
+	return s.handleFile(staticFileDir + "/sorttable.js")
+}
+
+func (s *Server) handleFile(name string) (http.HandlerFunc, error) {
+	bytes, err := ioutil.ReadFile(name)
+	if err != nil {
+		return nil, err
+	}
+	contentType := http.DetectContentType(bytes)
+	return func(w http.ResponseWriter, req *http.Request) {
+		w.Header().Set("Content-Type", contentType)
+		fmt.Fprintf(w, "%s", bytes)
+	}, nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/static/index.html
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/static/index.html b/traffic_monitor_golang/traffic_monitor/static/index.html
new file mode 100644
index 0000000..66cd03b
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/static/index.html
@@ -0,0 +1,560 @@
+<!DOCTYPE html>
+
+<!--
+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.
+-->
+
+
+<html>
+	<head>
+		<!-- <script src="sorttable.js"></script> -->
+		<meta charset="UTF-8">
+		<title>Traffic Monitor</title>
+		<style>
+		 body {
+			 font-family: "Lato", sans-serif;
+			 font-size: 14px;
+		 }
+
+		 ul.tab {
+			 list-style-type: none;
+			 margin: 0;
+			 padding: 0;
+			 overflow: hidden;
+			 border: 1px solid #ccc;
+			 background-color: #f1f1f1;
+		 }
+
+		 ul.tab li {float: left;}
+
+		 ul.tab li a {
+			 display: inline-block;
+			 color: black;
+			 text-align: center;
+			 padding: 8px 8px;
+			 text-decoration: none;
+			 transition: 0.3s;
+		 }
+
+		 ul.tab li a:hover {
+			 background-color: #cfd;
+		 }
+
+		 .tab-header-selected {
+			 background-color: #adb;
+		 }
+
+		 .tabcontent {
+			 display: none;
+			 padding: 6px 6px;
+			 border: 1px solid #ccc;
+			 border-top: none;
+		 }
+
+		 table, th, td {
+			 border: 0px solid black;
+		 }
+
+		 table {
+			 border-collapse: separate;
+			 border-spacing: 0px 0;
+			 width: 100%;
+		 }
+
+		 th, td {
+			 padding:5px 20px 5px 5px;
+		 }
+
+		 th {
+			 white-space: nowrap;
+		 }
+
+		 tr.stripes:nth-child(even) {
+			 background: #dfe
+		 }
+		 tr.stripes:nth-child(odd) {
+			 background: #fff
+		 }
+
+		 li.endpoint {
+			 margin: 4px 0;
+		 }
+
+		 ul {
+			 list-style: none;
+		 }
+
+		 #top-bar {
+			 border-collapse: collapse;
+			 border-color: #adb;;
+			 border-width: 0px 0px 1px 0px;
+			 padding-bottom: 10px;
+		 }
+
+		 .links {
+			 display: table;
+		 }
+		 .links ul {
+			 display: table-cell;
+			 vertical-align: top;
+		 }
+
+		 .error {
+			 background-color: #f00;
+		 }
+		 .warning {
+			 background-color: #f80;
+		 }
+		</style>
+		<script>
+		 function init() {
+			 openTab('cache-states-content');
+			 getTopBar();
+			 setInterval(getCacheCount, 4755);
+			 setInterval(getCacheAvailableCount, 4800);
+			 setInterval(getBandwidth, 4621);
+			 setInterval(getBandwidthCapacity, 4591);
+			 setInterval(getCacheDownCount, 4832);
+			 setInterval(getVersion, 10007); // change to retry on failure, and only do on startup
+			 setInterval(getTrafficOpsUri, 10019); // change to retry on failure, and only do on startup
+			 setInterval(getTrafficOpsCdn, 10500); // change to retry on failure, and only do on startup
+			 setInterval(getEvents, 2004); // change to retry on failure, and only do on startup
+			 setInterval(getCacheStatuses, 5009);
+			 setInterval(getDsStats, 4003);
+		 }
+
+		 // source: http://stackoverflow.com/a/2901298/292623
+		 function numberStrWithCommas(x) {
+			 return x.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+		 }
+
+		 function openTab(tabName) {
+			 var i, x, tablinks;
+			 x = document.getElementsByClassName("tabcontent");
+			 for (i = 0; i < x.length; i++) {
+				 x[i].style.display = "none";
+			 }
+			 tablinks = document.getElementsByClassName("tablinks");
+			 for (i = 0; i < x.length; i++) {
+				 tablinks[i].className = tablinks[i].className.replace(" tab-selected", "");
+			 }
+
+			 tabheaders = document.getElementsByClassName("tab-header");
+			 for (i = 0; i < tabheaders.length; i++) {
+				 tabheaders[i].className = tabheaders[i].className.replace(" tab-header-selected", "");
+			 }
+
+			 document.getElementById(tabName).style.display = "block";
+			 document.getElementById(tabName).className += " tab-selected";
+			 document.getElementById(tabName + "-tab").className += " tab-header-selected";
+		 }
+
+		 function getCacheCount() {
+			 ajax("/api/cache-count", function(r) {
+				 document.getElementById("cache-count").innerHTML = r;
+			 });
+		 }
+
+		 function getCacheAvailableCount() {
+			 ajax("/api/cache-available-count", function(r) {
+				 document.getElementById("cache-available").innerHTML = r;
+			 });
+		 }
+
+		 function getBandwidth() {
+			 ajax("/api/bandwidth-kbps", function(r) {
+				 document.getElementById("bandwidth").innerHTML = numberStrWithCommas((parseFloat(r) / kilobitsInGigabit).toFixed(2));
+			 });
+		 }
+
+		 function getBandwidthCapacity() {
+			 ajax("/api/bandwidth-capacity-kbps", function(r) {
+				 document.getElementById("bandwidth-capacity").innerHTML = numberStrWithCommas((r / kilobitsInGigabit).toString());
+			 });
+		 }
+
+		 function getCacheDownCount() {
+			 ajax("/api/cache-down-count", function(r) {
+				 document.getElementById("cache-down").innerHTML = r;
+			 });
+		 }
+
+		 function getVersion() {
+			 ajax("/api/version", function(r) {
+				 document.getElementById("version").innerHTML = r;
+			 });
+		 }
+
+		 function getTrafficOpsUri() {
+			 ajax("/api/traffic-ops-uri", function(r) {
+				 document.getElementById("source-uri").innerHTML = "<a href='" + r + "'>" + r + "</a>";
+			 });
+		 }
+
+
+		 function getTrafficOpsCdn() {
+			 ajax("/publish/ConfigDoc", function(r) {
+				 var j = JSON.parse(r);
+				 document.getElementById("cdn-name").innerHTML = j.cdnName;
+			 });
+		 }
+
+		 var lastEvent = 0;
+		 function getEvents() {
+			 /// \todo add /api/events-since/{index} (and change Traffic Monitor to keep latest
+			 ajax("/publish/EventLog", function(r) {
+				 var jdata = JSON.parse(r);
+				 for (i = jdata.events.length - 1; i >= 0; i--) {
+					 var event = jdata.events[i];
+					 if (event.index <= lastEvent) {
+						 continue;
+					 }
+					 lastEvent = event.index
+					 var row = document.getElementById("event-log").insertRow(1); //document.createElement("tr");
+					 row.classList.add("stripes");
+					 row.insertCell(0).id = row.id + "-name";
+					 document.getElementById(row.id + "-name").textContent = event.name;
+					 document.getElementById(row.id + "-name").style.whiteSpace = "nowrap";
+					 row.insertCell(1).textContent = event.type;
+					 row.insertCell(2).textContent = event.isAvailable ? "available" : "offline";
+					 if(event.isAvailable) {
+						 row.classList.add("stripes");
+						 row.classList.remove("error");
+					 } else {
+						 row.classList.add("error");
+						 row.classList.remove("stripes");
+					 }
+					 row.insertCell(3).textContent = event.description;
+					 row.insertCell(4).id = row.id + "-last";
+					 document.getElementById(row.id + "-last").textContent = new Date(event.time * 1000).toISOString();
+					 document.getElementById(row.id + "-last").style.whiteSpace = "nowrap";
+					 document.getElementById(row.id + "-last").style.textAlign = "right";
+				 }
+			 });
+		 }
+
+		 function getCacheStates() {
+			 ajax("/api/cache-statuses", function(r) {
+				 var jdata = JSON.parse(r);
+				 var servers = Object.keys(jdata); //debug
+				 for (i = 0; i < servers.length; i++) {
+					 var server = servers[i];
+					 if (!document.getElementById("cache-states-" + server)) {
+						 var row = document.getElementById("cache-states").insertRow(1); //document.createElement("tr");
+						 row.classList.add("stripes");
+						 row.id = "cache-states-" + server
+						 row.insertCell(0).id = row.id + "-server";
+						 row.insertCell(1).id = row.id + "-type";
+						 row.insertCell(2).id = row.id + "-status";
+						 row.insertCell(3).id = row.id + "-load-average";
+						 row.insertCell(4).id = row.id + "-query-time";
+						 row.insertCell(5).id = row.id + "-health-time";
+						 row.insertCell(6).id = row.id + "-stat-time";
+						 row.insertCell(7).id = row.id + "-health-span";
+						 row.insertCell(8).id = row.id + "-stat-span";
+						 row.insertCell(9).id = row.id + "-bandwidth";
+						 row.insertCell(10).id = row.id + "-connection-count";
+						 document.getElementById(row.id + "-server").textContent = server;
+						 document.getElementById(row.id + "-server").style.whiteSpace = "nowrap";
+						 document.getElementById(row.id + "-load-average").style.textAlign = "right";
+						 document.getElementById(row.id + "-query-time").style.textAlign = "right";
+						 document.getElementById(row.id + "-health-time").style.textAlign = "right";
+						 document.getElementById(row.id + "-stat-time").style.textAlign = "right";
+						 document.getElementById(row.id + "-health-span").style.textAlign = "right";
+						 document.getElementById(row.id + "-stat-span").style.textAlign = "right";
+						 document.getElementById(row.id + "-bandwidth").style.textAlign = "right";
+						 document.getElementById(row.id + "-connection-count").style.textAlign = "right";
+					 }
+
+					 /* \todo change to iterate over members, and make cells id constructed from these*/
+					 if (jdata[server].hasOwnProperty("type")) {
+						 document.getElementById("cache-states-" + server + "-type").textContent = jdata[server].type;
+					 }
+					 if (jdata[server].hasOwnProperty("status")) {
+						 document.getElementById("cache-states-" + server + "-status").textContent = jdata[server].status;
+						 var row = document.getElementById("cache-states-" + server);
+						 if (jdata[server].status.indexOf("ADMIN_DOWN") != -1) {
+							 row.classList.add("warning");
+							 row.classList.remove("error");
+							 row.classList.remove("stripes");
+						 } else if (jdata[server].status.indexOf(" available") == -1) {
+							 row.classList.add("error");
+							 row.classList.remove("warning");
+							 row.classList.remove("stripes");
+						 } else {
+							 row.classList.add("stripe");
+							 row.classList.remove("warning");
+							 row.classList.remove("error");
+						 }
+					 }
+					 if (jdata[server].hasOwnProperty("load_average")) {
+						 document.getElementById("cache-states-" + server + "-load-average").textContent = jdata[server].load_average;
+					 }
+					 if (jdata[server].hasOwnProperty("query_time_ms")) {
+						 document.getElementById("cache-states-" + server + "-query-time").textContent = jdata[server].query_time_ms;
+					 }
+					 if (jdata[server].hasOwnProperty("health_time_ms")) {
+						 document.getElementById("cache-states-" + server + "-health-time").textContent = jdata[server].health_time_ms;
+					 }
+					 if (jdata[server].hasOwnProperty("stat_time_ms")) {
+						 document.getElementById("cache-states-" + server + "-stat-time").textContent = jdata[server].stat_time_ms;
+					 }
+					 if (jdata[server].hasOwnProperty("health_span_ms")) {
+						 document.getElementById("cache-states-" + server + "-health-span").textContent = jdata[server].health_span_ms;
+					 }
+					 if (jdata[server].hasOwnProperty("stat_span_ms")) {
+						 document.getElementById("cache-states-" + server + "-stat-span").textContent = jdata[server].stat_span_ms;
+					 }
+					 if (jdata[server].hasOwnProperty("bandwidth_kbps")) {
+						 var kbps = (jdata[server].bandwidth_kbps / kilobitsInMegabit).toFixed(2);
+						 var max = numberStrWithCommas((jdata[server].bandwidth_capacity_kbps / kilobitsInMegabit).toFixed(0));
+						 document.getElementById("cache-states-" + server + "-bandwidth").textContent = '' + kbps + ' / ' + max;
+					 } else {
+						 document.getElementById("cache-states-" + server + "-bandwidth").textContent = "N/A";
+					 }
+					 if (jdata[server].hasOwnProperty("connection_count")) {
+						 document.getElementById("cache-states-" + server + "-connection-count").textContent = jdata[server].connection_count;
+					 } else {
+						 document.getElementById("cache-states-" + server + "-connection-count").textContent = "N/A";
+					 }
+				 }
+			 })
+		 }
+
+		 var millisecondsInSecond = 1000;
+		 var kilobitsInGigabit = 1000000;
+		 var kilobitsInMegabit = 1000;
+
+		 // dsDisplayFloat takes a float, and returns the string to display. For nonzero values, it returns two decimal places. For zero values, it returns an empty string, to make nonzero values more visible.
+		 function dsDisplayFloat(f) {
+			 var s = f
+			 if (f != 0.0) {
+				 s = f.toFixed(2);
+			 }
+			 return s
+		 }
+
+		 function getDsStats() {
+			 var now = Date.now();
+
+			 /// \todo add /api/delivery-service-stats which only returns the data needed by the UI, for efficiency
+			 ajax("/publish/DsStats", function(r) {
+				 var j = JSON.parse(r);
+				 var jds = j.deliveryService
+				 var deliveryServiceNames = Object.keys(jds); //debug
+				 //decrementing for loop so DsNames are alphabetical A-Z
+				 //TODO allow for filtering of columns so this isn't necessary
+					for (var i = deliveryServiceNames.length - 1; i >= 0; i--) {
+					 var deliveryService = deliveryServiceNames[i];
+
+					 if (!document.getElementById("deliveryservice-stats-" + deliveryService)) {
+						 var row = document.getElementById("deliveryservice-stats").insertRow(1); //document.createElement("tr");
+						 row.id = "deliveryservice-stats-" + deliveryService
+						 row.insertCell(0).id = row.id + "-delivery-service";
+						 row.insertCell(1).id = row.id + "-status";
+						 row.insertCell(2).id = row.id + "-caches-reporting";
+						 row.insertCell(3).id = row.id + "-bandwidth";
+						 row.insertCell(4).id = row.id + "-tps";
+						 row.insertCell(5).id = row.id + "-2xx";
+						 row.insertCell(6).id = row.id + "-3xx";
+						 row.insertCell(7).id = row.id + "-4xx";
+						 row.insertCell(8).id = row.id + "-5xx";
+						 row.insertCell(9).id = row.id + "-disabled-locations";
+						 document.getElementById(row.id + "-delivery-service").textContent = deliveryService;
+						 document.getElementById(row.id + "-delivery-service").style.whiteSpace = "nowrap";
+						 document.getElementById(row.id + "-caches-reporting").style.textAlign = "right";
+						 document.getElementById(row.id + "-bandwidth").style.textAlign = "right";
+						 document.getElementById(row.id + "-tps").style.textAlign = "right";
+						 document.getElementById(row.id + "-2xx").style.textAlign = "right";
+						 document.getElementById(row.id + "-3xx").style.textAlign = "right";
+						 document.getElementById(row.id + "-4xx").style.textAlign = "right";
+						 document.getElementById(row.id + "-5xx").style.textAlign = "right";
+					 }
+
+					 // \todo check that array has a member before dereferencing [0]
+					 if (jds[deliveryService].hasOwnProperty("isAvailable")) {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-status").textContent = jds[deliveryService]["isAvailable"][0].value == "true" ? "available" : "unavailable - " + jds[deliveryService]["error-string"][0].value;
+					 }
+					 if (jds[deliveryService].hasOwnProperty("caches-reporting") && jds[deliveryService].hasOwnProperty("caches-available") && jds[deliveryService].hasOwnProperty("caches-configured")) {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-caches-reporting").textContent = jds[deliveryService]['caches-reporting'][0].value + " / " + jds[deliveryService]['caches-available'][0].value + " / " + jds[deliveryService]['caches-configured'][0].value;
+					 }
+					 if (jds[deliveryService].hasOwnProperty("total.kbps")) {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-bandwidth").textContent = (jds[deliveryService]['total.kbps'][0].value / kilobitsInMegabit).toFixed(2);
+					 } else {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-bandwidth").textContent = "N/A";
+					 }
+					 if (jds[deliveryService].hasOwnProperty("total.tps_total")) {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-tps").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_total'][0].value));
+					 } else {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-tps").textContent = "N/A";
+					 }
+					 if (jds[deliveryService].hasOwnProperty("total.tps_2xx")) {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-2xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_2xx'][0].value));
+					 } else {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-2xx").textContent = "N/A";
+					 }
+					 if (jds[deliveryService].hasOwnProperty("total.tps_3xx")) {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-3xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_3xx'][0].value));
+					 } else {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-3xx").textContent = "N/A";
+					 }
+					 if (jds[deliveryService].hasOwnProperty("total.tps_4xx")) {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-4xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_4xx'][0].value));
+					 } else {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-4xx").textContent = "N/A";
+					 }
+					 if (jds[deliveryService].hasOwnProperty("total.tps_5xx")) {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-5xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_5xx'][0].value));
+					 } else {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-5xx").textContent = "N/A";
+					 }
+
+					 // \todo implement disabled locations
+
+					 var row = document.getElementById("deliveryservice-stats-" + deliveryService);
+					 if (jds[deliveryService]["isAvailable"][0].value == "true") {
+						 row.classList.add("stripes");
+						 row.classList.remove("error");
+					 } else {
+						 row.classList.add("error");
+						 row.classList.remove("stripes");
+					 }
+				 }
+			 })
+		 }
+
+		 function getCacheStatuses() {
+			 getCacheCount();
+			 getCacheAvailableCount();
+			 getCacheDownCount();
+			 getCacheStates();
+		 }
+
+		 function getTopBar() {
+			 getVersion();
+			 getTrafficOpsUri();
+			 getTrafficOpsCdn();
+			 getCacheStatuses();
+		 }
+
+		 function ajax(endpoint, f) {
+			 var xhttp = new XMLHttpRequest();
+			 xhttp.onreadystatechange = function() {
+				 if (xhttp.readyState == 4 && xhttp.status == 200) {
+					 f(xhttp.responseText);
+				 }
+			 };
+			 xhttp.open("GET", endpoint, true);
+			 xhttp.send();
+		 }
+		</script>
+	</head>
+	<body onload="init()">
+
+		<table id="top-bar">
+			<tr>
+				<td>Caches: count=<span id="cache-count">0</span> available=<span id="cache-available">0</span> down=<span id="cache-down">0</span> </td>
+				<td>Bandwidth: <span id="bandwidth">0</span> / <span id="bandwidth-capacity">\u221e</span> gbps</td>
+				<td>Traffic Ops: <span id="source-uri">unknown</span></td>
+				<td>CDN: <span id="cdn-name">unknown</span></td>
+				<td>Version: <span id="version">unknown</span></td>
+			</tr>
+		</table>
+
+		<div class="links">
+			<ul>
+				<li class="endpoint"><a href="/publish/EventLog">EventLog</a></li>
+				<li class="endpoint"><a href="/publish/CacheStats">CacheStats</a></li>
+				<li class="endpoint"><a href="/publish/DsStats">DsStats</a></li>
+				<li class="endpoint"><a href="/publish/CrStates">CrStates (as published to Traffic Routers)</a></li>
+				<li class="endpoint"><a href="/publish/CrConfig">CrConfig (json)</a></li>
+				<li class="endpoint"><a href="/publish/PeerStates">PeerStates</a></li>
+				<li class="endpoint"><a href="/publish/Stats">Stats</a></li>
+				<li class="endpoint"><a href="/publish/StatSummary">StatSummary</a></li>
+				<li class="endpoint"><a href="/publish/ConfigDoc">ConfigDoc</a></li>
+			</ul>
+
+			<ul>
+				<li class="endpoint"><a href="/api/cache-count">/api/cache-count</a></li>
+				<li class="endpoint"><a href="/api/cache-available-count">/api/cache-available-count</a></li>
+				<li class="endpoint"><a href="/api/cache-down-count">/api/cache-down-count</a></li>
+				<li class="endpoint"><a href="/api/version">/api/version</a></li>
+				<li class="endpoint"><a href="/api/traffic-ops-uri">/api/traffic-ops-uri</a></li>
+				<li class="endpoint"><a href="/api/cache-statuses">/api/cache-statuses</a></li>
+				<li class="endpoint"><a href="/api/bandwidth-kbps">/api/bandwidth-kbps</a></li>
+				<li class="endpoint"><a href="/api/bandwidth-capacity-kbps">/api/bandwidth-capacity-kbps</a></li>
+			</ul>
+		</div>
+
+		<ul class="tab">
+			<li id="cache-states-content-tab" class="tab-header"><a href="#" onclick="openTab('cache-states-content')" class="tablinks">Cache States</a></li>
+			<li id="deliveryservice-stats-content-tab" class="tab-header"><a href="#" onclick="openTab('deliveryservice-stats-content')" class="tablinks">Delivery Service States</a></li>
+			<li id="event-log-content-tab" class="tab-header"><a href="#" onclick="openTab('event-log-content')" class="tablinks">Event Log</a></li>
+		</ul>
+
+		<div id="cache-states-content" class="tabcontent">
+			<table id="cache-states" class="tab-grid sortable">
+				<tr>
+					<th>Server</th>
+					<th>Type</th>
+					<th>Status</th>
+					<th align="right">Load Average</th>
+					<th align="right">Query Time (ms)</th>
+					<th align="right">Health Time (ms)</th>
+					<th align="right">Stat Time (ms)</th>
+					<th align="right">Health Span (ms)</th>
+					<th align="right">Stat Span (ms)</th>
+					<th align="right">Bandwidth (mbps)</th>
+					<th align="right">Connection Count</th>
+				</tr>
+			</table>
+		</div>
+		<div id="deliveryservice-stats-content" class="tabcontent">
+			<table id="deliveryservice-stats" class="tab-grid sortable">
+				<tr>
+					<th>Delivery Service</th>
+					<th>Status</th>
+					<th align="right">Caches Reporting/Available/Configured</th>
+					<th align="right">Bandwidth (mbps)</th>
+					<th align="right">t/sec</th>
+					<th align="right">2xx/sec</th>
+					<th align="right">3xx/sec</th>
+					<th align="right">4xx/sec</th>
+					<th align="right">5xx/sec</th>
+					<th>Disabled Locations</th>
+				</tr>
+			</table>
+		</div>
+
+		<div id="event-log-content" class="tabcontent">
+			<table id="event-log" class="tab-grid sortable">
+				<tr>
+					<th>Name</th>
+					<th>Type</th>
+					<th>Status</th>
+					<th>Description</th>
+					<th align="center" id="event-log-last-header">Event Time</th>
+				</tr>
+			</table>
+		</div>
+
+		<div id="update-num-text">Number of updates: <span id="update-num">0</span></div>
+		<div id="last-val-text">Last Val: <span id="last-val">0</span></div>
+		<a href="/">Refresh Server List</a>
+	</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/static/sorttable.js
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/static/sorttable.js b/traffic_monitor_golang/traffic_monitor/static/sorttable.js
new file mode 100644
index 0000000..38b0fc6
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/static/sorttable.js
@@ -0,0 +1,495 @@
+/*
+  SortTable
+  version 2
+  7th April 2007
+  Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
+
+  Instructions:
+  Download this file
+  Add <script src="sorttable.js"></script> to your HTML
+  Add class="sortable" to any table you'd like to make sortable
+  Click on the headers to sort
+
+  Thanks to many, many people for contributions and suggestions.
+  Licenced as X11: http://www.kryogenix.org/code/browser/licence.html
+  This basically means: do what you want with it.
+*/
+
+
+var stIsIE = /*@cc_on!@*/false;
+
+sorttable = {
+  init: function() {
+    // quit if this function has already been called
+    if (arguments.callee.done) return;
+    // flag this function so we don't do the same thing twice
+    arguments.callee.done = true;
+    // kill the timer
+    if (_timer) clearInterval(_timer);
+
+    if (!document.createElement || !document.getElementsByTagName) return;
+
+    sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/;
+
+    forEach(document.getElementsByTagName('table'), function(table) {
+      if (table.className.search(/\bsortable\b/) != -1) {
+        sorttable.makeSortable(table);
+      }
+    });
+
+  },
+
+  makeSortable: function(table) {
+    if (table.getElementsByTagName('thead').length == 0) {
+      // table doesn't have a tHead. Since it should have, create one and
+      // put the first table row in it.
+      the = document.createElement('thead');
+      the.appendChild(table.rows[0]);
+      table.insertBefore(the,table.firstChild);
+    }
+    // Safari doesn't support table.tHead, sigh
+    if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0];
+
+    if (table.tHead.rows.length != 1) return; // can't cope with two header rows
+
+    // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as
+    // "total" rows, for example). This is B&R, since what you're supposed
+    // to do is put them in a tfoot. So, if there are sortbottom rows,
+    // for backwards compatibility, move them to tfoot (creating it if needed).
+    sortbottomrows = [];
+    for (var i=0; i<table.rows.length; i++) {
+      if (table.rows[i].className.search(/\bsortbottom\b/) != -1) {
+        sortbottomrows[sortbottomrows.length] = table.rows[i];
+      }
+    }
+    if (sortbottomrows) {
+      if (table.tFoot == null) {
+        // table doesn't have a tfoot. Create one.
+        tfo = document.createElement('tfoot');
+        table.appendChild(tfo);
+      }
+      for (var i=0; i<sortbottomrows.length; i++) {
+        tfo.appendChild(sortbottomrows[i]);
+      }
+      delete sortbottomrows;
+    }
+
+    // work through each column and calculate its type
+    headrow = table.tHead.rows[0].cells;
+    for (var i=0; i<headrow.length; i++) {
+      // manually override the type with a sorttable_type attribute
+      if (!headrow[i].className.match(/\bsorttable_nosort\b/)) { // skip this col
+        mtch = headrow[i].className.match(/\bsorttable_([a-z0-9]+)\b/);
+        if (mtch) { override = mtch[1]; }
+	      if (mtch && typeof sorttable["sort_"+override] == 'function') {
+	        headrow[i].sorttable_sortfunction = sorttable["sort_"+override];
+	      } else {
+	        headrow[i].sorttable_sortfunction = sorttable.guessType(table,i);
+	      }
+	      // make it clickable to sort
+	      headrow[i].sorttable_columnindex = i;
+	      headrow[i].sorttable_tbody = table.tBodies[0];
+	      dean_addEvent(headrow[i],"click", sorttable.innerSortFunction = function(e) {
+
+          if (this.className.search(/\bsorttable_sorted\b/) != -1) {
+            // if we're already sorted by this column, just
+            // reverse the table, which is quicker
+            sorttable.reverse(this.sorttable_tbody);
+            this.className = this.className.replace('sorttable_sorted',
+                                                    'sorttable_sorted_reverse');
+            this.removeChild(document.getElementById('sorttable_sortfwdind'));
+            sortrevind = document.createElement('span');
+            sortrevind.id = "sorttable_sortrevind";
+            sortrevind.innerHTML = stIsIE ? '&nbsp<font face="webdings">5</font>' : '&nbsp;&#x25B4;';
+            this.appendChild(sortrevind);
+            return;
+          }
+          if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) {
+            // if we're already sorted by this column in reverse, just
+            // re-reverse the table, which is quicker
+            sorttable.reverse(this.sorttable_tbody);
+            this.className = this.className.replace('sorttable_sorted_reverse',
+                                                    'sorttable_sorted');
+            this.removeChild(document.getElementById('sorttable_sortrevind'));
+            sortfwdind = document.createElement('span');
+            sortfwdind.id = "sorttable_sortfwdind";
+            sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
+            this.appendChild(sortfwdind);
+            return;
+          }
+
+          // remove sorttable_sorted classes
+          theadrow = this.parentNode;
+          forEach(theadrow.childNodes, function(cell) {
+            if (cell.nodeType == 1) { // an element
+              cell.className = cell.className.replace('sorttable_sorted_reverse','');
+              cell.className = cell.className.replace('sorttable_sorted','');
+            }
+          });
+          sortfwdind = document.getElementById('sorttable_sortfwdind');
+          if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); }
+          sortrevind = document.getElementById('sorttable_sortrevind');
+          if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); }
+
+          this.className += ' sorttable_sorted';
+          sortfwdind = document.createElement('span');
+          sortfwdind.id = "sorttable_sortfwdind";
+          sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
+          this.appendChild(sortfwdind);
+
+	        // build an array to sort. This is a Schwartzian transform thing,
+	        // i.e., we "decorate" each row with the actual sort key,
+	        // sort based on the sort keys, and then put the rows back in order
+	        // which is a lot faster because you only do getInnerText once per row
+	        row_array = [];
+	        col = this.sorttable_columnindex;
+	        rows = this.sorttable_tbody.rows;
+	        for (var j=0; j<rows.length; j++) {
+	          row_array[row_array.length] = [sorttable.getInnerText(rows[j].cells[col]), rows[j]];
+	        }
+	        /* If you want a stable sort, uncomment the following line */
+	        //sorttable.shaker_sort(row_array, this.sorttable_sortfunction);
+	        /* and comment out this one */
+	        row_array.sort(this.sorttable_sortfunction);
+
+	        tb = this.sorttable_tbody;
+	        for (var j=0; j<row_array.length; j++) {
+	          tb.appendChild(row_array[j][1]);
+	        }
+
+	        delete row_array;
+	      });
+	    }
+    }
+  },
+
+  guessType: function(table, column) {
+    // guess the type of a column based on its first non-blank row
+    sortfn = sorttable.sort_alpha;
+    for (var i=0; i<table.tBodies[0].rows.length; i++) {
+      text = sorttable.getInnerText(table.tBodies[0].rows[i].cells[column]);
+      if (text != '') {
+        if (text.match(/^-?[\ufffd$\ufffd]?[\d,.]+%?$/)) {
+          return sorttable.sort_numeric;
+        }
+        // check for a date: dd/mm/yyyy or dd/mm/yy
+        // can have / or . or - as separator
+        // can be mm/dd as well
+        possdate = text.match(sorttable.DATE_RE)
+        if (possdate) {
+          // looks like a date
+          first = parseInt(possdate[1]);
+          second = parseInt(possdate[2]);
+          if (first > 12) {
+            // definitely dd/mm
+            return sorttable.sort_ddmm;
+          } else if (second > 12) {
+            return sorttable.sort_mmdd;
+          } else {
+            // looks like a date, but we can't tell which, so assume
+            // that it's dd/mm (English imperialism!) and keep looking
+            sortfn = sorttable.sort_ddmm;
+          }
+        }
+      }
+    }
+    return sortfn;
+  },
+
+  getInnerText: function(node) {
+    // gets the text we want to use for sorting for a cell.
+    // strips leading and trailing whitespace.
+    // this is *not* a generic getInnerText function; it's special to sorttable.
+    // for example, you can override the cell text with a customkey attribute.
+    // it also gets .value for <input> fields.
+
+    if (!node) return "";
+
+    hasInputs = (typeof node.getElementsByTagName == 'function') &&
+                 node.getElementsByTagName('input').length;
+
+    if (node.getAttribute("sorttable_customkey") != null) {
+      return node.getAttribute("sorttable_customkey");
+    }
+    else if (typeof node.textContent != 'undefined' && !hasInputs) {
+      return node.textContent.replace(/^\s+|\s+$/g, '');
+    }
+    else if (typeof node.innerText != 'undefined' && !hasInputs) {
+      return node.innerText.replace(/^\s+|\s+$/g, '');
+    }
+    else if (typeof node.text != 'undefined' && !hasInputs) {
+      return node.text.replace(/^\s+|\s+$/g, '');
+    }
+    else {
+      switch (node.nodeType) {
+        case 3:
+          if (node.nodeName.toLowerCase() == 'input') {
+            return node.value.replace(/^\s+|\s+$/g, '');
+          }
+        case 4:
+          return node.nodeValue.replace(/^\s+|\s+$/g, '');
+          break;
+        case 1:
+        case 11:
+          var innerText = '';
+          for (var i = 0; i < node.childNodes.length; i++) {
+            innerText += sorttable.getInnerText(node.childNodes[i]);
+          }
+          return innerText.replace(/^\s+|\s+$/g, '');
+          break;
+        default:
+          return '';
+      }
+    }
+  },
+
+  reverse: function(tbody) {
+    // reverse the rows in a tbody
+    newrows = [];
+    for (var i=0; i<tbody.rows.length; i++) {
+      newrows[newrows.length] = tbody.rows[i];
+    }
+    for (var i=newrows.length-1; i>=0; i--) {
+       tbody.appendChild(newrows[i]);
+    }
+    delete newrows;
+  },
+
+  /* sort functions
+     each sort function takes two parameters, a and b
+     you are comparing a[0] and b[0] */
+  sort_numeric: function(a,b) {
+    aa = parseFloat(a[0].replace(/[^0-9.-]/g,''));
+    if (isNaN(aa)) aa = 0;
+    bb = parseFloat(b[0].replace(/[^0-9.-]/g,''));
+    if (isNaN(bb)) bb = 0;
+    return aa-bb;
+  },
+  sort_alpha: function(a,b) {
+    if (a[0]==b[0]) return 0;
+    if (a[0]<b[0]) return -1;
+    return 1;
+  },
+  sort_ddmm: function(a,b) {
+    mtch = a[0].match(sorttable.DATE_RE);
+    y = mtch[3]; m = mtch[2]; d = mtch[1];
+    if (m.length == 1) m = '0'+m;
+    if (d.length == 1) d = '0'+d;
+    dt1 = y+m+d;
+    mtch = b[0].match(sorttable.DATE_RE);
+    y = mtch[3]; m = mtch[2]; d = mtch[1];
+    if (m.length == 1) m = '0'+m;
+    if (d.length == 1) d = '0'+d;
+    dt2 = y+m+d;
+    if (dt1==dt2) return 0;
+    if (dt1<dt2) return -1;
+    return 1;
+  },
+  sort_mmdd: function(a,b) {
+    mtch = a[0].match(sorttable.DATE_RE);
+    y = mtch[3]; d = mtch[2]; m = mtch[1];
+    if (m.length == 1) m = '0'+m;
+    if (d.length == 1) d = '0'+d;
+    dt1 = y+m+d;
+    mtch = b[0].match(sorttable.DATE_RE);
+    y = mtch[3]; d = mtch[2]; m = mtch[1];
+    if (m.length == 1) m = '0'+m;
+    if (d.length == 1) d = '0'+d;
+    dt2 = y+m+d;
+    if (dt1==dt2) return 0;
+    if (dt1<dt2) return -1;
+    return 1;
+  },
+
+  shaker_sort: function(list, comp_func) {
+    // A stable sort function to allow multi-level sorting of data
+    // see: http://en.wikipedia.org/wiki/Cocktail_sort
+    // thanks to Joseph Nahmias
+    var b = 0;
+    var t = list.length - 1;
+    var swap = true;
+
+    while(swap) {
+        swap = false;
+        for(var i = b; i < t; ++i) {
+            if ( comp_func(list[i], list[i+1]) > 0 ) {
+                var q = list[i]; list[i] = list[i+1]; list[i+1] = q;
+                swap = true;
+            }
+        } // for
+        t--;
+
+        if (!swap) break;
+
+        for(var i = t; i > b; --i) {
+            if ( comp_func(list[i], list[i-1]) < 0 ) {
+                var q = list[i]; list[i] = list[i-1]; list[i-1] = q;
+                swap = true;
+            }
+        } // for
+        b++;
+
+    } // while(swap)
+  }
+}
+
+/* ******************************************************************
+   Supporting functions: bundled here to avoid depending on a library
+   ****************************************************************** */
+
+// Dean Edwards/Matthias Miller/John Resig
+
+/* for Mozilla/Opera9 */
+if (document.addEventListener) {
+    document.addEventListener("DOMContentLoaded", sorttable.init, false);
+}
+
+/* for Internet Explorer */
+/*@cc_on @*/
+/*@if (@_win32)
+    document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
+    var script = document.getElementById("__ie_onload");
+    script.onreadystatechange = function() {
+        if (this.readyState == "complete") {
+            sorttable.init(); // call the onload handler
+        }
+    };
+/*@end @*/
+
+/* for Safari */
+if (/WebKit/i.test(navigator.userAgent)) { // sniff
+    var _timer = setInterval(function() {
+        if (/loaded|complete/.test(document.readyState)) {
+            sorttable.init(); // call the onload handler
+        }
+    }, 10);
+}
+
+/* for other browsers */
+window.onload = sorttable.init;
+
+// written by Dean Edwards, 2005
+// with input from Tino Zijdel, Matthias Miller, Diego Perini
+
+// http://dean.edwards.name/weblog/2005/10/add-event/
+
+function dean_addEvent(element, type, handler) {
+	if (element.addEventListener) {
+		element.addEventListener(type, handler, false);
+	} else {
+		// assign each event handler a unique ID
+		if (!handler.$$guid) handler.$$guid = dean_addEvent.guid++;
+		// create a hash table of event types for the element
+		if (!element.events) element.events = {};
+		// create a hash table of event handlers for each element/event pair
+		var handlers = element.events[type];
+		if (!handlers) {
+			handlers = element.events[type] = {};
+			// store the existing event handler (if there is one)
+			if (element["on" + type]) {
+				handlers[0] = element["on" + type];
+			}
+		}
+		// store the event handler in the hash table
+		handlers[handler.$$guid] = handler;
+		// assign a global event handler to do all the work
+		element["on" + type] = handleEvent;
+	}
+};
+// a counter used to create unique IDs
+dean_addEvent.guid = 1;
+
+function removeEvent(element, type, handler) {
+	if (element.removeEventListener) {
+		element.removeEventListener(type, handler, false);
+	} else {
+		// delete the event handler from the hash table
+		if (element.events && element.events[type]) {
+			delete element.events[type][handler.$$guid];
+		}
+	}
+};
+
+function handleEvent(event) {
+	var returnValue = true;
+	// grab the event object (IE uses a global event object)
+	event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
+	// get a reference to the hash table of event handlers
+	var handlers = this.events[event.type];
+	// execute each event handler
+	for (var i in handlers) {
+		this.$$handleEvent = handlers[i];
+		if (this.$$handleEvent(event) === false) {
+			returnValue = false;
+		}
+	}
+	return returnValue;
+};
+
+function fixEvent(event) {
+	// add W3C standard event methods
+	event.preventDefault = fixEvent.preventDefault;
+	event.stopPropagation = fixEvent.stopPropagation;
+	return event;
+};
+fixEvent.preventDefault = function() {
+	this.returnValue = false;
+};
+fixEvent.stopPropagation = function() {
+  this.cancelBubble = true;
+}
+
+// Dean's forEach: http://dean.edwards.name/base/forEach.js
+/*
+	forEach, version 1.0
+	Copyright 2006, Dean Edwards
+	License: http://www.opensource.org/licenses/mit-license.php
+*/
+
+// array-like enumeration
+if (!Array.forEach) { // mozilla already supports this
+	Array.forEach = function(array, block, context) {
+		for (var i = 0; i < array.length; i++) {
+			block.call(context, array[i], i, array);
+		}
+	};
+}
+
+// generic enumeration
+Function.prototype.forEach = function(object, block, context) {
+	for (var key in object) {
+		if (typeof this.prototype[key] == "undefined") {
+			block.call(context, object[key], key, object);
+		}
+	}
+};
+
+// character enumeration
+String.forEach = function(string, block, context) {
+	Array.forEach(string.split(""), function(chr, index) {
+		block.call(context, chr, index, string);
+	});
+};
+
+// globally resolve forEach enumeration
+var forEach = function(object, block, context) {
+	if (object) {
+		var resolve = Object; // default
+		if (object instanceof Function) {
+			// functions have a "length" property
+			resolve = Function;
+		} else if (object.forEach instanceof Function) {
+			// the object implements a custom forEach method so use that
+			object.forEach(block, context);
+			return;
+		} else if (typeof object == "string") {
+			// the object is a string
+			resolve = String;
+		} else if (typeof object.length == "number") {
+			// the object is array-like
+			resolve = Array;
+		}
+		resolve.forEach(object, block, context);
+	}
+};
+

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/threadsafe/cacheavailablestatus.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/threadsafe/cacheavailablestatus.go b/traffic_monitor_golang/traffic_monitor/threadsafe/cacheavailablestatus.go
new file mode 100644
index 0000000..52fed62
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/threadsafe/cacheavailablestatus.go
@@ -0,0 +1,52 @@
+package threadsafe
+
+/*
+ * 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 (
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/cache"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+	"sync"
+)
+
+// CacheAvailableStatus wraps a map of cache available statuses to be safe for multiple reader goroutines and one writer.
+type CacheAvailableStatus struct {
+	caches *cache.AvailableStatuses
+	m      *sync.RWMutex
+}
+
+// NewCacheAvailableStatus creates and returns a new CacheAvailableStatus, initializing internal pointer values.
+func NewCacheAvailableStatus() CacheAvailableStatus {
+	c := cache.AvailableStatuses(map[enum.CacheName]cache.AvailableStatus{})
+	return CacheAvailableStatus{m: &sync.RWMutex{}, caches: &c}
+}
+
+// Get returns the internal map of cache statuses. The returned map MUST NOT be modified. If modification is necessary, copy.
+func (o *CacheAvailableStatus) Get() cache.AvailableStatuses {
+	o.m.RLock()
+	defer o.m.RUnlock()
+	return *o.caches
+}
+
+// Set sets the internal map of cache availability. This MUST NOT be called by multiple goroutines.
+func (o *CacheAvailableStatus) Set(v cache.AvailableStatuses) {
+	o.m.Lock()
+	*o.caches = v
+	o.m.Unlock()
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/threadsafe/cachemaxkbpses.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/threadsafe/cachemaxkbpses.go b/traffic_monitor_golang/traffic_monitor/threadsafe/cachemaxkbpses.go
new file mode 100644
index 0000000..ec3cd46
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/threadsafe/cachemaxkbpses.go
@@ -0,0 +1,52 @@
+package threadsafe
+
+/*
+ * 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 (
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/cache"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+	"sync"
+)
+
+// CacheAvailableStatus wraps a map of cache available statuses to be safe for multiple reader goroutines and one writer.
+type CacheKbpses struct {
+	v *cache.Kbpses
+	m *sync.RWMutex
+}
+
+// NewCacheAvailableStatus creates and returns a new CacheAvailableStatus, initializing internal pointer values.
+func NewCacheKbpses() CacheKbpses {
+	v := cache.Kbpses(map[enum.CacheName]int64{})
+	return CacheKbpses{m: &sync.RWMutex{}, v: &v}
+}
+
+// Get returns the internal map of cache statuses. The returned map MUST NOT be modified. If modification is necessary, copy.
+func (o *CacheKbpses) Get() cache.Kbpses {
+	o.m.RLock()
+	defer o.m.RUnlock()
+	return *o.v
+}
+
+// Set sets the internal map of cache availability. This MUST NOT be called by multiple goroutines.
+func (o *CacheKbpses) Set(v cache.Kbpses) {
+	o.m.Lock()
+	*o.v = v
+	o.m.Unlock()
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/threadsafe/dsstats.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/threadsafe/dsstats.go b/traffic_monitor_golang/traffic_monitor/threadsafe/dsstats.go
new file mode 100644
index 0000000..165d201
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/threadsafe/dsstats.go
@@ -0,0 +1,57 @@
+package threadsafe
+
+/*
+ * 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 (
+	"sync"
+
+	dsdata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/deliveryservicedata"
+)
+
+// DSStats wraps a deliveryservice.Stats object to be safe for multiple reader goroutines and a single writer.
+type DSStats struct {
+	dsStats *dsdata.Stats
+	m       *sync.RWMutex
+}
+
+// DSStatsReader permits reading of a dsdata.Stats object, but not writing. This is designed so a Stats object can safely be passed to multiple goroutines, without worry one may unsafely write.
+type DSStatsReader interface {
+	Get() dsdata.StatsReadonly
+}
+
+// NewDSStats returns a deliveryservice.Stats object wrapped to be safe for multiple readers and a single writer.
+func NewDSStats() DSStats {
+	s := dsdata.NewStats()
+	return DSStats{m: &sync.RWMutex{}, dsStats: &s}
+}
+
+// Get returns a Stats object safe for reading by multiple goroutines
+func (o *DSStats) Get() dsdata.StatsReadonly {
+	o.m.RLock()
+	defer o.m.RUnlock()
+	return *o.dsStats
+}
+
+// Set sets the internal Stats object. This MUST NOT be called by multiple goroutines.
+func (o *DSStats) Set(newDsStats dsdata.Stats) {
+	o.m.Lock()
+	*o.dsStats = newDsStats
+	o.m.Unlock()
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/threadsafe/lastkbpsstats.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/threadsafe/lastkbpsstats.go b/traffic_monitor_golang/traffic_monitor/threadsafe/lastkbpsstats.go
new file mode 100644
index 0000000..55eb165
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/threadsafe/lastkbpsstats.go
@@ -0,0 +1,52 @@
+package threadsafe
+
+/*
+ * 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 (
+	"sync"
+
+	dsdata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/deliveryservicedata"
+)
+
+// LastStats wraps a deliveryservice.LastStats object to be safe for multiple readers and one writer.
+type LastStats struct {
+	stats *dsdata.LastStats
+	m     *sync.RWMutex
+}
+
+// NewLastStats returns a wrapped a deliveryservice.LastStats object safe for multiple readers and one writer.
+func NewLastStats() LastStats {
+	s := dsdata.NewLastStats()
+	return LastStats{m: &sync.RWMutex{}, stats: &s}
+}
+
+// Get returns the last KBPS stats object. Callers MUST NOT modify the object. It is not threadsafe for writing. If the object must be modified, callers must call LastStats.Copy() and modify the copy.
+func (o *LastStats) Get() dsdata.LastStats {
+	o.m.RLock()
+	defer o.m.RUnlock()
+	return *o.stats
+}
+
+// Set sets the internal LastStats object. This MUST NOT be called by multiple goroutines.
+func (o *LastStats) Set(s dsdata.LastStats) {
+	o.m.Lock()
+	*o.stats = s
+	o.m.Unlock()
+}



[19/19] incubator-trafficcontrol git commit: This closes #234

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


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

Branch: refs/heads/master
Commit: 499bcbc29cf4a53073b3d04eb8cca1c4d28404ce
Parents: a722d92
Author: Dave Neuman <ne...@apache.org>
Authored: Mon Jan 30 08:29:01 2017 -0700
Committer: Dave Neuman <ne...@apache.org>
Committed: Mon Jan 30 08:29:01 2017 -0700

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

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



[03/19] incubator-trafficcontrol git commit: Move TM2 to trafficcontrol/traffic_monitor_golang

Posted by ne...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/windows.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/windows.go b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/windows.go
new file mode 100644
index 0000000..c836bdb
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/windows.go
@@ -0,0 +1,561 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build windows
+
+package fsnotify
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+	"runtime"
+	"sync"
+	"syscall"
+	"unsafe"
+)
+
+// Watcher watches a set of files, delivering events to a channel.
+type Watcher struct {
+	Events   chan Event
+	Errors   chan error
+	isClosed bool           // Set to true when Close() is first called
+	mu       sync.Mutex     // Map access
+	port     syscall.Handle // Handle to completion port
+	watches  watchMap       // Map of watches (key: i-number)
+	input    chan *input    // Inputs to the reader are sent on this channel
+	quit     chan chan<- error
+}
+
+// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
+func NewWatcher() (*Watcher, error) {
+	port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
+	if e != nil {
+		return nil, os.NewSyscallError("CreateIoCompletionPort", e)
+	}
+	w := &Watcher{
+		port:    port,
+		watches: make(watchMap),
+		input:   make(chan *input, 1),
+		Events:  make(chan Event, 50),
+		Errors:  make(chan error),
+		quit:    make(chan chan<- error, 1),
+	}
+	go w.readEvents()
+	return w, nil
+}
+
+// Close removes all watches and closes the events channel.
+func (w *Watcher) Close() error {
+	if w.isClosed {
+		return nil
+	}
+	w.isClosed = true
+
+	// Send "quit" message to the reader goroutine
+	ch := make(chan error)
+	w.quit <- ch
+	if err := w.wakeupReader(); err != nil {
+		return err
+	}
+	return <-ch
+}
+
+// Add starts watching the named file or directory (non-recursively).
+func (w *Watcher) Add(name string) error {
+	if w.isClosed {
+		return errors.New("watcher already closed")
+	}
+	in := &input{
+		op:    opAddWatch,
+		path:  filepath.Clean(name),
+		flags: sysFSALLEVENTS,
+		reply: make(chan error),
+	}
+	w.input <- in
+	if err := w.wakeupReader(); err != nil {
+		return err
+	}
+	return <-in.reply
+}
+
+// Remove stops watching the the named file or directory (non-recursively).
+func (w *Watcher) Remove(name string) error {
+	in := &input{
+		op:    opRemoveWatch,
+		path:  filepath.Clean(name),
+		reply: make(chan error),
+	}
+	w.input <- in
+	if err := w.wakeupReader(); err != nil {
+		return err
+	}
+	return <-in.reply
+}
+
+const (
+	// Options for AddWatch
+	sysFSONESHOT = 0x80000000
+	sysFSONLYDIR = 0x1000000
+
+	// Events
+	sysFSACCESS     = 0x1
+	sysFSALLEVENTS  = 0xfff
+	sysFSATTRIB     = 0x4
+	sysFSCLOSE      = 0x18
+	sysFSCREATE     = 0x100
+	sysFSDELETE     = 0x200
+	sysFSDELETESELF = 0x400
+	sysFSMODIFY     = 0x2
+	sysFSMOVE       = 0xc0
+	sysFSMOVEDFROM  = 0x40
+	sysFSMOVEDTO    = 0x80
+	sysFSMOVESELF   = 0x800
+
+	// Special events
+	sysFSIGNORED   = 0x8000
+	sysFSQOVERFLOW = 0x4000
+)
+
+func newEvent(name string, mask uint32) Event {
+	e := Event{Name: name}
+	if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
+		e.Op |= Create
+	}
+	if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
+		e.Op |= Remove
+	}
+	if mask&sysFSMODIFY == sysFSMODIFY {
+		e.Op |= Write
+	}
+	if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
+		e.Op |= Rename
+	}
+	if mask&sysFSATTRIB == sysFSATTRIB {
+		e.Op |= Chmod
+	}
+	return e
+}
+
+const (
+	opAddWatch = iota
+	opRemoveWatch
+)
+
+const (
+	provisional uint64 = 1 << (32 + iota)
+)
+
+type input struct {
+	op    int
+	path  string
+	flags uint32
+	reply chan error
+}
+
+type inode struct {
+	handle syscall.Handle
+	volume uint32
+	index  uint64
+}
+
+type watch struct {
+	ov     syscall.Overlapped
+	ino    *inode            // i-number
+	path   string            // Directory path
+	mask   uint64            // Directory itself is being watched with these notify flags
+	names  map[string]uint64 // Map of names being watched and their notify flags
+	rename string            // Remembers the old name while renaming a file
+	buf    [4096]byte
+}
+
+type indexMap map[uint64]*watch
+type watchMap map[uint32]indexMap
+
+func (w *Watcher) wakeupReader() error {
+	e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
+	if e != nil {
+		return os.NewSyscallError("PostQueuedCompletionStatus", e)
+	}
+	return nil
+}
+
+func getDir(pathname string) (dir string, err error) {
+	attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
+	if e != nil {
+		return "", os.NewSyscallError("GetFileAttributes", e)
+	}
+	if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
+		dir = pathname
+	} else {
+		dir, _ = filepath.Split(pathname)
+		dir = filepath.Clean(dir)
+	}
+	return
+}
+
+func getIno(path string) (ino *inode, err error) {
+	h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
+		syscall.FILE_LIST_DIRECTORY,
+		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
+		nil, syscall.OPEN_EXISTING,
+		syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
+	if e != nil {
+		return nil, os.NewSyscallError("CreateFile", e)
+	}
+	var fi syscall.ByHandleFileInformation
+	if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
+		syscall.CloseHandle(h)
+		return nil, os.NewSyscallError("GetFileInformationByHandle", e)
+	}
+	ino = &inode{
+		handle: h,
+		volume: fi.VolumeSerialNumber,
+		index:  uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
+	}
+	return ino, nil
+}
+
+// Must run within the I/O thread.
+func (m watchMap) get(ino *inode) *watch {
+	if i := m[ino.volume]; i != nil {
+		return i[ino.index]
+	}
+	return nil
+}
+
+// Must run within the I/O thread.
+func (m watchMap) set(ino *inode, watch *watch) {
+	i := m[ino.volume]
+	if i == nil {
+		i = make(indexMap)
+		m[ino.volume] = i
+	}
+	i[ino.index] = watch
+}
+
+// Must run within the I/O thread.
+func (w *Watcher) addWatch(pathname string, flags uint64) error {
+	dir, err := getDir(pathname)
+	if err != nil {
+		return err
+	}
+	if flags&sysFSONLYDIR != 0 && pathname != dir {
+		return nil
+	}
+	ino, err := getIno(dir)
+	if err != nil {
+		return err
+	}
+	w.mu.Lock()
+	watchEntry := w.watches.get(ino)
+	w.mu.Unlock()
+	if watchEntry == nil {
+		if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
+			syscall.CloseHandle(ino.handle)
+			return os.NewSyscallError("CreateIoCompletionPort", e)
+		}
+		watchEntry = &watch{
+			ino:   ino,
+			path:  dir,
+			names: make(map[string]uint64),
+		}
+		w.mu.Lock()
+		w.watches.set(ino, watchEntry)
+		w.mu.Unlock()
+		flags |= provisional
+	} else {
+		syscall.CloseHandle(ino.handle)
+	}
+	if pathname == dir {
+		watchEntry.mask |= flags
+	} else {
+		watchEntry.names[filepath.Base(pathname)] |= flags
+	}
+	if err = w.startRead(watchEntry); err != nil {
+		return err
+	}
+	if pathname == dir {
+		watchEntry.mask &= ^provisional
+	} else {
+		watchEntry.names[filepath.Base(pathname)] &= ^provisional
+	}
+	return nil
+}
+
+// Must run within the I/O thread.
+func (w *Watcher) remWatch(pathname string) error {
+	dir, err := getDir(pathname)
+	if err != nil {
+		return err
+	}
+	ino, err := getIno(dir)
+	if err != nil {
+		return err
+	}
+	w.mu.Lock()
+	watch := w.watches.get(ino)
+	w.mu.Unlock()
+	if watch == nil {
+		return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
+	}
+	if pathname == dir {
+		w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
+		watch.mask = 0
+	} else {
+		name := filepath.Base(pathname)
+		w.sendEvent(watch.path+"\\"+name, watch.names[name]&sysFSIGNORED)
+		delete(watch.names, name)
+	}
+	return w.startRead(watch)
+}
+
+// Must run within the I/O thread.
+func (w *Watcher) deleteWatch(watch *watch) {
+	for name, mask := range watch.names {
+		if mask&provisional == 0 {
+			w.sendEvent(watch.path+"\\"+name, mask&sysFSIGNORED)
+		}
+		delete(watch.names, name)
+	}
+	if watch.mask != 0 {
+		if watch.mask&provisional == 0 {
+			w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
+		}
+		watch.mask = 0
+	}
+}
+
+// Must run within the I/O thread.
+func (w *Watcher) startRead(watch *watch) error {
+	if e := syscall.CancelIo(watch.ino.handle); e != nil {
+		w.Errors <- os.NewSyscallError("CancelIo", e)
+		w.deleteWatch(watch)
+	}
+	mask := toWindowsFlags(watch.mask)
+	for _, m := range watch.names {
+		mask |= toWindowsFlags(m)
+	}
+	if mask == 0 {
+		if e := syscall.CloseHandle(watch.ino.handle); e != nil {
+			w.Errors <- os.NewSyscallError("CloseHandle", e)
+		}
+		w.mu.Lock()
+		delete(w.watches[watch.ino.volume], watch.ino.index)
+		w.mu.Unlock()
+		return nil
+	}
+	e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
+		uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
+	if e != nil {
+		err := os.NewSyscallError("ReadDirectoryChanges", e)
+		if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
+			// Watched directory was probably removed
+			if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) {
+				if watch.mask&sysFSONESHOT != 0 {
+					watch.mask = 0
+				}
+			}
+			err = nil
+		}
+		w.deleteWatch(watch)
+		w.startRead(watch)
+		return err
+	}
+	return nil
+}
+
+// readEvents reads from the I/O completion port, converts the
+// received events into Event objects and sends them via the Events channel.
+// Entry point to the I/O thread.
+func (w *Watcher) readEvents() {
+	var (
+		n, key uint32
+		ov     *syscall.Overlapped
+	)
+	runtime.LockOSThread()
+
+	for {
+		e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
+		watch := (*watch)(unsafe.Pointer(ov))
+
+		if watch == nil {
+			select {
+			case ch := <-w.quit:
+				w.mu.Lock()
+				var indexes []indexMap
+				for _, index := range w.watches {
+					indexes = append(indexes, index)
+				}
+				w.mu.Unlock()
+				for _, index := range indexes {
+					for _, watch := range index {
+						w.deleteWatch(watch)
+						w.startRead(watch)
+					}
+				}
+				var err error
+				if e := syscall.CloseHandle(w.port); e != nil {
+					err = os.NewSyscallError("CloseHandle", e)
+				}
+				close(w.Events)
+				close(w.Errors)
+				ch <- err
+				return
+			case in := <-w.input:
+				switch in.op {
+				case opAddWatch:
+					in.reply <- w.addWatch(in.path, uint64(in.flags))
+				case opRemoveWatch:
+					in.reply <- w.remWatch(in.path)
+				}
+			default:
+			}
+			continue
+		}
+
+		switch e {
+		case syscall.ERROR_MORE_DATA:
+			if watch == nil {
+				w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
+			} else {
+				// The i/o succeeded but the buffer is full.
+				// In theory we should be building up a full packet.
+				// In practice we can get away with just carrying on.
+				n = uint32(unsafe.Sizeof(watch.buf))
+			}
+		case syscall.ERROR_ACCESS_DENIED:
+			// Watched directory was probably removed
+			w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
+			w.deleteWatch(watch)
+			w.startRead(watch)
+			continue
+		case syscall.ERROR_OPERATION_ABORTED:
+			// CancelIo was called on this handle
+			continue
+		default:
+			w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
+			continue
+		case nil:
+		}
+
+		var offset uint32
+		for {
+			if n == 0 {
+				w.Events <- newEvent("", sysFSQOVERFLOW)
+				w.Errors <- errors.New("short read in readEvents()")
+				break
+			}
+
+			// Point "raw" to the event in the buffer
+			raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
+			buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
+			name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
+			fullname := watch.path + "\\" + name
+
+			var mask uint64
+			switch raw.Action {
+			case syscall.FILE_ACTION_REMOVED:
+				mask = sysFSDELETESELF
+			case syscall.FILE_ACTION_MODIFIED:
+				mask = sysFSMODIFY
+			case syscall.FILE_ACTION_RENAMED_OLD_NAME:
+				watch.rename = name
+			case syscall.FILE_ACTION_RENAMED_NEW_NAME:
+				if watch.names[watch.rename] != 0 {
+					watch.names[name] |= watch.names[watch.rename]
+					delete(watch.names, watch.rename)
+					mask = sysFSMOVESELF
+				}
+			}
+
+			sendNameEvent := func() {
+				if w.sendEvent(fullname, watch.names[name]&mask) {
+					if watch.names[name]&sysFSONESHOT != 0 {
+						delete(watch.names, name)
+					}
+				}
+			}
+			if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
+				sendNameEvent()
+			}
+			if raw.Action == syscall.FILE_ACTION_REMOVED {
+				w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
+				delete(watch.names, name)
+			}
+			if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
+				if watch.mask&sysFSONESHOT != 0 {
+					watch.mask = 0
+				}
+			}
+			if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
+				fullname = watch.path + "\\" + watch.rename
+				sendNameEvent()
+			}
+
+			// Move to the next event in the buffer
+			if raw.NextEntryOffset == 0 {
+				break
+			}
+			offset += raw.NextEntryOffset
+
+			// Error!
+			if offset >= n {
+				w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
+				break
+			}
+		}
+
+		if err := w.startRead(watch); err != nil {
+			w.Errors <- err
+		}
+	}
+}
+
+func (w *Watcher) sendEvent(name string, mask uint64) bool {
+	if mask == 0 {
+		return false
+	}
+	event := newEvent(name, uint32(mask))
+	select {
+	case ch := <-w.quit:
+		w.quit <- ch
+	case w.Events <- event:
+	}
+	return true
+}
+
+func toWindowsFlags(mask uint64) uint32 {
+	var m uint32
+	if mask&sysFSACCESS != 0 {
+		m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
+	}
+	if mask&sysFSMODIFY != 0 {
+		m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
+	}
+	if mask&sysFSATTRIB != 0 {
+		m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
+	}
+	if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
+		m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
+	}
+	return m
+}
+
+func toFSnotifyFlags(action uint32) uint64 {
+	switch action {
+	case syscall.FILE_ACTION_ADDED:
+		return sysFSCREATE
+	case syscall.FILE_ACTION_REMOVED:
+		return sysFSDELETE
+	case syscall.FILE_ACTION_MODIFIED:
+		return sysFSMODIFY
+	case syscall.FILE_ACTION_RENAMED_OLD_NAME:
+		return sysFSMOVEDFROM
+	case syscall.FILE_ACTION_RENAMED_NEW_NAME:
+		return sysFSMOVEDTO
+	}
+	return 0
+}


[04/19] incubator-trafficcontrol git commit: Move TM2 to trafficcontrol/traffic_monitor_golang

Posted by ne...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/fen.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/fen.go b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/fen.go
new file mode 100644
index 0000000..ced39cb
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/fen.go
@@ -0,0 +1,37 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build solaris
+
+package fsnotify
+
+import (
+	"errors"
+)
+
+// Watcher watches a set of files, delivering events to a channel.
+type Watcher struct {
+	Events chan Event
+	Errors chan error
+}
+
+// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
+func NewWatcher() (*Watcher, error) {
+	return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
+}
+
+// Close removes all watches and closes the events channel.
+func (w *Watcher) Close() error {
+	return nil
+}
+
+// Add starts watching the named file or directory (non-recursively).
+func (w *Watcher) Add(name string) error {
+	return nil
+}
+
+// Remove stops watching the the named file or directory (non-recursively).
+func (w *Watcher) Remove(name string) error {
+	return nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/fsnotify.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/fsnotify.go b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/fsnotify.go
new file mode 100644
index 0000000..d1d39a0
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/fsnotify.go
@@ -0,0 +1,62 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !plan9
+
+// Package fsnotify provides a platform-independent interface for file system notifications.
+package fsnotify
+
+import (
+	"bytes"
+	"fmt"
+)
+
+// Event represents a single file system notification.
+type Event struct {
+	Name string // Relative path to the file or directory.
+	Op   Op     // File operation that triggered the event.
+}
+
+// Op describes a set of file operations.
+type Op uint32
+
+// These are the generalized file operations that can trigger a notification.
+const (
+	Create Op = 1 << iota
+	Write
+	Remove
+	Rename
+	Chmod
+)
+
+// String returns a string representation of the event in the form
+// "file: REMOVE|WRITE|..."
+func (e Event) String() string {
+	// Use a buffer for efficient string concatenation
+	var buffer bytes.Buffer
+
+	if e.Op&Create == Create {
+		buffer.WriteString("|CREATE")
+	}
+	if e.Op&Remove == Remove {
+		buffer.WriteString("|REMOVE")
+	}
+	if e.Op&Write == Write {
+		buffer.WriteString("|WRITE")
+	}
+	if e.Op&Rename == Rename {
+		buffer.WriteString("|RENAME")
+	}
+	if e.Op&Chmod == Chmod {
+		buffer.WriteString("|CHMOD")
+	}
+
+	// If buffer remains empty, return no event names
+	if buffer.Len() == 0 {
+		return fmt.Sprintf("%q: ", e.Name)
+	}
+
+	// Return a list of event names, with leading pipe character stripped
+	return fmt.Sprintf("%q: %s", e.Name, buffer.String()[1:])
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify.go b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify.go
new file mode 100644
index 0000000..9700df5
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify.go
@@ -0,0 +1,325 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build linux
+
+package fsnotify
+
+import (
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+	"unsafe"
+
+	"golang.org/x/sys/unix"
+)
+
+// Watcher watches a set of files, delivering events to a channel.
+type Watcher struct {
+	Events   chan Event
+	Errors   chan error
+	mu       sync.Mutex // Map access
+	cv       *sync.Cond // sync removing on rm_watch with IN_IGNORE
+	fd       int
+	poller   *fdPoller
+	watches  map[string]*watch // Map of inotify watches (key: path)
+	paths    map[int]string    // Map of watched paths (key: watch descriptor)
+	done     chan struct{}     // Channel for sending a "quit message" to the reader goroutine
+	doneResp chan struct{}     // Channel to respond to Close
+}
+
+// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
+func NewWatcher() (*Watcher, error) {
+	// Create inotify fd
+	fd, errno := unix.InotifyInit()
+	if fd == -1 {
+		return nil, errno
+	}
+	// Create epoll
+	poller, err := newFdPoller(fd)
+	if err != nil {
+		unix.Close(fd)
+		return nil, err
+	}
+	w := &Watcher{
+		fd:       fd,
+		poller:   poller,
+		watches:  make(map[string]*watch),
+		paths:    make(map[int]string),
+		Events:   make(chan Event),
+		Errors:   make(chan error),
+		done:     make(chan struct{}),
+		doneResp: make(chan struct{}),
+	}
+	w.cv = sync.NewCond(&w.mu)
+
+	go w.readEvents()
+	return w, nil
+}
+
+func (w *Watcher) isClosed() bool {
+	select {
+	case <-w.done:
+		return true
+	default:
+		return false
+	}
+}
+
+// Close removes all watches and closes the events channel.
+func (w *Watcher) Close() error {
+	if w.isClosed() {
+		return nil
+	}
+
+	// Send 'close' signal to goroutine, and set the Watcher to closed.
+	close(w.done)
+
+	// Wake up goroutine
+	w.poller.wake()
+
+	// Wait for goroutine to close
+	<-w.doneResp
+
+	return nil
+}
+
+// Add starts watching the named file or directory (non-recursively).
+func (w *Watcher) Add(name string) error {
+	name = filepath.Clean(name)
+	if w.isClosed() {
+		return errors.New("inotify instance already closed")
+	}
+
+	const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
+		unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
+		unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
+
+	var flags uint32 = agnosticEvents
+
+	w.mu.Lock()
+	watchEntry, found := w.watches[name]
+	w.mu.Unlock()
+	if found {
+		watchEntry.flags |= flags
+		flags |= unix.IN_MASK_ADD
+	}
+	wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
+	if wd == -1 {
+		return errno
+	}
+
+	w.mu.Lock()
+	w.watches[name] = &watch{wd: uint32(wd), flags: flags}
+	w.paths[wd] = name
+	w.mu.Unlock()
+
+	return nil
+}
+
+// Remove stops watching the named file or directory (non-recursively).
+func (w *Watcher) Remove(name string) error {
+	name = filepath.Clean(name)
+
+	// Fetch the watch.
+	w.mu.Lock()
+	defer w.mu.Unlock()
+	watch, ok := w.watches[name]
+
+	// Remove it from inotify.
+	if !ok {
+		return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
+	}
+	// inotify_rm_watch will return EINVAL if the file has been deleted;
+	// the inotify will already have been removed.
+	// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
+	// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
+	// so that EINVAL means that the wd is being rm_watch()ed or its file removed
+	// by another thread and we have not received IN_IGNORE event.
+	success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
+	if success == -1 {
+		// TODO: Perhaps it's not helpful to return an error here in every case.
+		// the only two possible errors are:
+		// EBADF, which happens when w.fd is not a valid file descriptor of any kind.
+		// EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor.
+		// Watch descriptors are invalidated when they are removed explicitly or implicitly;
+		// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted.
+		return errno
+	}
+
+	// wait until ignoreLinux() deleting maps
+	exists := true
+	for exists {
+		w.cv.Wait()
+		_, exists = w.watches[name]
+	}
+
+	return nil
+}
+
+type watch struct {
+	wd    uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
+	flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
+}
+
+// readEvents reads from the inotify file descriptor, converts the
+// received events into Event objects and sends them via the Events channel
+func (w *Watcher) readEvents() {
+	var (
+		buf   [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
+		n     int                                  // Number of bytes read with read()
+		errno error                                // Syscall errno
+		ok    bool                                 // For poller.wait
+	)
+
+	defer close(w.doneResp)
+	defer close(w.Errors)
+	defer close(w.Events)
+	defer unix.Close(w.fd)
+	defer w.poller.close()
+
+	for {
+		// See if we have been closed.
+		if w.isClosed() {
+			return
+		}
+
+		ok, errno = w.poller.wait()
+		if errno != nil {
+			select {
+			case w.Errors <- errno:
+			case <-w.done:
+				return
+			}
+			continue
+		}
+
+		if !ok {
+			continue
+		}
+
+		n, errno = unix.Read(w.fd, buf[:])
+		// If a signal interrupted execution, see if we've been asked to close, and try again.
+		// http://man7.org/linux/man-pages/man7/signal.7.html :
+		// "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable"
+		if errno == unix.EINTR {
+			continue
+		}
+
+		// unix.Read might have been woken up by Close. If so, we're done.
+		if w.isClosed() {
+			return
+		}
+
+		if n < unix.SizeofInotifyEvent {
+			var err error
+			if n == 0 {
+				// If EOF is received. This should really never happen.
+				err = io.EOF
+			} else if n < 0 {
+				// If an error occurred while reading.
+				err = errno
+			} else {
+				// Read was too short.
+				err = errors.New("notify: short read in readEvents()")
+			}
+			select {
+			case w.Errors <- err:
+			case <-w.done:
+				return
+			}
+			continue
+		}
+
+		var offset uint32
+		// We don't know how many events we just read into the buffer
+		// While the offset points to at least one whole event...
+		for offset <= uint32(n-unix.SizeofInotifyEvent) {
+			// Point "raw" to the event in the buffer
+			raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
+
+			mask := uint32(raw.Mask)
+			nameLen := uint32(raw.Len)
+			// If the event happened to the watched directory or the watched file, the kernel
+			// doesn't append the filename to the event, but we would like to always fill the
+			// the "Name" field with a valid filename. We retrieve the path of the watch from
+			// the "paths" map.
+			w.mu.Lock()
+			name := w.paths[int(raw.Wd)]
+			w.mu.Unlock()
+			if nameLen > 0 {
+				// Point "bytes" at the first byte of the filename
+				bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))
+				// The filename is padded with NULL bytes. TrimRight() gets rid of those.
+				name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
+			}
+
+			event := newEvent(name, mask)
+
+			// Send the events that are not ignored on the events channel
+			if !event.ignoreLinux(w, raw.Wd, mask) {
+				select {
+				case w.Events <- event:
+				case <-w.done:
+					return
+				}
+			}
+
+			// Move to the next event in the buffer
+			offset += unix.SizeofInotifyEvent + nameLen
+		}
+	}
+}
+
+// Certain types of events can be "ignored" and not sent over the Events
+// channel. Such as events marked ignore by the kernel, or MODIFY events
+// against files that do not exist.
+func (e *Event) ignoreLinux(w *Watcher, wd int32, mask uint32) bool {
+	// Ignore anything the inotify API says to ignore
+	if mask&unix.IN_IGNORED == unix.IN_IGNORED {
+		w.mu.Lock()
+		defer w.mu.Unlock()
+		name := w.paths[int(wd)]
+		delete(w.paths, int(wd))
+		delete(w.watches, name)
+		w.cv.Broadcast()
+		return true
+	}
+
+	// If the event is not a DELETE or RENAME, the file must exist.
+	// Otherwise the event is ignored.
+	// *Note*: this was put in place because it was seen that a MODIFY
+	// event was sent after the DELETE. This ignores that MODIFY and
+	// assumes a DELETE will come or has come if the file doesn't exist.
+	if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
+		_, statErr := os.Lstat(e.Name)
+		return os.IsNotExist(statErr)
+	}
+	return false
+}
+
+// newEvent returns an platform-independent Event based on an inotify mask.
+func newEvent(name string, mask uint32) Event {
+	e := Event{Name: name}
+	if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
+		e.Op |= Create
+	}
+	if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
+		e.Op |= Remove
+	}
+	if mask&unix.IN_MODIFY == unix.IN_MODIFY {
+		e.Op |= Write
+	}
+	if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
+		e.Op |= Rename
+	}
+	if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
+		e.Op |= Chmod
+	}
+	return e
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify_poller.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify_poller.go b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify_poller.go
new file mode 100644
index 0000000..cc7db4b
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify_poller.go
@@ -0,0 +1,187 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build linux
+
+package fsnotify
+
+import (
+	"errors"
+
+	"golang.org/x/sys/unix"
+)
+
+type fdPoller struct {
+	fd   int    // File descriptor (as returned by the inotify_init() syscall)
+	epfd int    // Epoll file descriptor
+	pipe [2]int // Pipe for waking up
+}
+
+func emptyPoller(fd int) *fdPoller {
+	poller := new(fdPoller)
+	poller.fd = fd
+	poller.epfd = -1
+	poller.pipe[0] = -1
+	poller.pipe[1] = -1
+	return poller
+}
+
+// Create a new inotify poller.
+// This creates an inotify handler, and an epoll handler.
+func newFdPoller(fd int) (*fdPoller, error) {
+	var errno error
+	poller := emptyPoller(fd)
+	defer func() {
+		if errno != nil {
+			poller.close()
+		}
+	}()
+	poller.fd = fd
+
+	// Create epoll fd
+	poller.epfd, errno = unix.EpollCreate1(0)
+	if poller.epfd == -1 {
+		return nil, errno
+	}
+	// Create pipe; pipe[0] is the read end, pipe[1] the write end.
+	errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK)
+	if errno != nil {
+		return nil, errno
+	}
+
+	// Register inotify fd with epoll
+	event := unix.EpollEvent{
+		Fd:     int32(poller.fd),
+		Events: unix.EPOLLIN,
+	}
+	errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event)
+	if errno != nil {
+		return nil, errno
+	}
+
+	// Register pipe fd with epoll
+	event = unix.EpollEvent{
+		Fd:     int32(poller.pipe[0]),
+		Events: unix.EPOLLIN,
+	}
+	errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event)
+	if errno != nil {
+		return nil, errno
+	}
+
+	return poller, nil
+}
+
+// Wait using epoll.
+// Returns true if something is ready to be read,
+// false if there is not.
+func (poller *fdPoller) wait() (bool, error) {
+	// 3 possible events per fd, and 2 fds, makes a maximum of 6 events.
+	// I don't know whether epoll_wait returns the number of events returned,
+	// or the total number of events ready.
+	// I decided to catch both by making the buffer one larger than the maximum.
+	events := make([]unix.EpollEvent, 7)
+	for {
+		n, errno := unix.EpollWait(poller.epfd, events, -1)
+		if n == -1 {
+			if errno == unix.EINTR {
+				continue
+			}
+			return false, errno
+		}
+		if n == 0 {
+			// If there are no events, try again.
+			continue
+		}
+		if n > 6 {
+			// This should never happen. More events were returned than should be possible.
+			return false, errors.New("epoll_wait returned more events than I know what to do with")
+		}
+		ready := events[:n]
+		epollhup := false
+		epollerr := false
+		epollin := false
+		for _, event := range ready {
+			if event.Fd == int32(poller.fd) {
+				if event.Events&unix.EPOLLHUP != 0 {
+					// This should not happen, but if it does, treat it as a wakeup.
+					epollhup = true
+				}
+				if event.Events&unix.EPOLLERR != 0 {
+					// If an error is waiting on the file descriptor, we should pretend
+					// something is ready to read, and let unix.Read pick up the error.
+					epollerr = true
+				}
+				if event.Events&unix.EPOLLIN != 0 {
+					// There is data to read.
+					epollin = true
+				}
+			}
+			if event.Fd == int32(poller.pipe[0]) {
+				if event.Events&unix.EPOLLHUP != 0 {
+					// Write pipe descriptor was closed, by us. This means we're closing down the
+					// watcher, and we should wake up.
+				}
+				if event.Events&unix.EPOLLERR != 0 {
+					// If an error is waiting on the pipe file descriptor.
+					// This is an absolute mystery, and should never ever happen.
+					return false, errors.New("Error on the pipe descriptor.")
+				}
+				if event.Events&unix.EPOLLIN != 0 {
+					// This is a regular wakeup, so we have to clear the buffer.
+					err := poller.clearWake()
+					if err != nil {
+						return false, err
+					}
+				}
+			}
+		}
+
+		if epollhup || epollerr || epollin {
+			return true, nil
+		}
+		return false, nil
+	}
+}
+
+// Close the write end of the poller.
+func (poller *fdPoller) wake() error {
+	buf := make([]byte, 1)
+	n, errno := unix.Write(poller.pipe[1], buf)
+	if n == -1 {
+		if errno == unix.EAGAIN {
+			// Buffer is full, poller will wake.
+			return nil
+		}
+		return errno
+	}
+	return nil
+}
+
+func (poller *fdPoller) clearWake() error {
+	// You have to be woken up a LOT in order to get to 100!
+	buf := make([]byte, 100)
+	n, errno := unix.Read(poller.pipe[0], buf)
+	if n == -1 {
+		if errno == unix.EAGAIN {
+			// Buffer is empty, someone else cleared our wake.
+			return nil
+		}
+		return errno
+	}
+	return nil
+}
+
+// Close all poller file descriptors, but not the one passed to it.
+func (poller *fdPoller) close() {
+	if poller.pipe[1] != -1 {
+		unix.Close(poller.pipe[1])
+	}
+	if poller.pipe[0] != -1 {
+		unix.Close(poller.pipe[0])
+	}
+	if poller.epfd != -1 {
+		unix.Close(poller.epfd)
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify_poller_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify_poller_test.go b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify_poller_test.go
new file mode 100644
index 0000000..26623ef
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify_poller_test.go
@@ -0,0 +1,229 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build linux
+
+package fsnotify
+
+import (
+	"testing"
+	"time"
+
+	"golang.org/x/sys/unix"
+)
+
+type testFd [2]int
+
+func makeTestFd(t *testing.T) testFd {
+	var tfd testFd
+	errno := unix.Pipe(tfd[:])
+	if errno != nil {
+		t.Fatalf("Failed to create pipe: %v", errno)
+	}
+	return tfd
+}
+
+func (tfd testFd) fd() int {
+	return tfd[0]
+}
+
+func (tfd testFd) closeWrite(t *testing.T) {
+	errno := unix.Close(tfd[1])
+	if errno != nil {
+		t.Fatalf("Failed to close write end of pipe: %v", errno)
+	}
+}
+
+func (tfd testFd) put(t *testing.T) {
+	buf := make([]byte, 10)
+	_, errno := unix.Write(tfd[1], buf)
+	if errno != nil {
+		t.Fatalf("Failed to write to pipe: %v", errno)
+	}
+}
+
+func (tfd testFd) get(t *testing.T) {
+	buf := make([]byte, 10)
+	_, errno := unix.Read(tfd[0], buf)
+	if errno != nil {
+		t.Fatalf("Failed to read from pipe: %v", errno)
+	}
+}
+
+func (tfd testFd) close() {
+	unix.Close(tfd[1])
+	unix.Close(tfd[0])
+}
+
+func makePoller(t *testing.T) (testFd, *fdPoller) {
+	tfd := makeTestFd(t)
+	poller, err := newFdPoller(tfd.fd())
+	if err != nil {
+		t.Fatalf("Failed to create poller: %v", err)
+	}
+	return tfd, poller
+}
+
+func TestPollerWithBadFd(t *testing.T) {
+	_, err := newFdPoller(-1)
+	if err != unix.EBADF {
+		t.Fatalf("Expected EBADF, got: %v", err)
+	}
+}
+
+func TestPollerWithData(t *testing.T) {
+	tfd, poller := makePoller(t)
+	defer tfd.close()
+	defer poller.close()
+
+	tfd.put(t)
+	ok, err := poller.wait()
+	if err != nil {
+		t.Fatalf("poller failed: %v", err)
+	}
+	if !ok {
+		t.Fatalf("expected poller to return true")
+	}
+	tfd.get(t)
+}
+
+func TestPollerWithWakeup(t *testing.T) {
+	tfd, poller := makePoller(t)
+	defer tfd.close()
+	defer poller.close()
+
+	err := poller.wake()
+	if err != nil {
+		t.Fatalf("wake failed: %v", err)
+	}
+	ok, err := poller.wait()
+	if err != nil {
+		t.Fatalf("poller failed: %v", err)
+	}
+	if ok {
+		t.Fatalf("expected poller to return false")
+	}
+}
+
+func TestPollerWithClose(t *testing.T) {
+	tfd, poller := makePoller(t)
+	defer tfd.close()
+	defer poller.close()
+
+	tfd.closeWrite(t)
+	ok, err := poller.wait()
+	if err != nil {
+		t.Fatalf("poller failed: %v", err)
+	}
+	if !ok {
+		t.Fatalf("expected poller to return true")
+	}
+}
+
+func TestPollerWithWakeupAndData(t *testing.T) {
+	tfd, poller := makePoller(t)
+	defer tfd.close()
+	defer poller.close()
+
+	tfd.put(t)
+	err := poller.wake()
+	if err != nil {
+		t.Fatalf("wake failed: %v", err)
+	}
+
+	// both data and wakeup
+	ok, err := poller.wait()
+	if err != nil {
+		t.Fatalf("poller failed: %v", err)
+	}
+	if !ok {
+		t.Fatalf("expected poller to return true")
+	}
+
+	// data is still in the buffer, wakeup is cleared
+	ok, err = poller.wait()
+	if err != nil {
+		t.Fatalf("poller failed: %v", err)
+	}
+	if !ok {
+		t.Fatalf("expected poller to return true")
+	}
+
+	tfd.get(t)
+	// data is gone, only wakeup now
+	err = poller.wake()
+	if err != nil {
+		t.Fatalf("wake failed: %v", err)
+	}
+	ok, err = poller.wait()
+	if err != nil {
+		t.Fatalf("poller failed: %v", err)
+	}
+	if ok {
+		t.Fatalf("expected poller to return false")
+	}
+}
+
+func TestPollerConcurrent(t *testing.T) {
+	tfd, poller := makePoller(t)
+	defer tfd.close()
+	defer poller.close()
+
+	oks := make(chan bool)
+	live := make(chan bool)
+	defer close(live)
+	go func() {
+		defer close(oks)
+		for {
+			ok, err := poller.wait()
+			if err != nil {
+				t.Fatalf("poller failed: %v", err)
+			}
+			oks <- ok
+			if !<-live {
+				return
+			}
+		}
+	}()
+
+	// Try a write
+	select {
+	case <-time.After(50 * time.Millisecond):
+	case <-oks:
+		t.Fatalf("poller did not wait")
+	}
+	tfd.put(t)
+	if !<-oks {
+		t.Fatalf("expected true")
+	}
+	tfd.get(t)
+	live <- true
+
+	// Try a wakeup
+	select {
+	case <-time.After(50 * time.Millisecond):
+	case <-oks:
+		t.Fatalf("poller did not wait")
+	}
+	err := poller.wake()
+	if err != nil {
+		t.Fatalf("wake failed: %v", err)
+	}
+	if <-oks {
+		t.Fatalf("expected false")
+	}
+	live <- true
+
+	// Try a close
+	select {
+	case <-time.After(50 * time.Millisecond):
+	case <-oks:
+		t.Fatalf("poller did not wait")
+	}
+	tfd.closeWrite(t)
+	if !<-oks {
+		t.Fatalf("expected true")
+	}
+	tfd.get(t)
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify_test.go b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify_test.go
new file mode 100644
index 0000000..2527cad
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/inotify_test.go
@@ -0,0 +1,344 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build linux
+
+package fsnotify
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"testing"
+	"time"
+
+	"golang.org/x/sys/unix"
+)
+
+func TestInotifyCloseRightAway(t *testing.T) {
+	w, err := NewWatcher()
+	if err != nil {
+		t.Fatalf("Failed to create watcher")
+	}
+
+	// Close immediately; it won't even reach the first unix.Read.
+	w.Close()
+
+	// Wait for the close to complete.
+	<-time.After(50 * time.Millisecond)
+	isWatcherReallyClosed(t, w)
+}
+
+func TestInotifyCloseSlightlyLater(t *testing.T) {
+	w, err := NewWatcher()
+	if err != nil {
+		t.Fatalf("Failed to create watcher")
+	}
+
+	// Wait until readEvents has reached unix.Read, and Close.
+	<-time.After(50 * time.Millisecond)
+	w.Close()
+
+	// Wait for the close to complete.
+	<-time.After(50 * time.Millisecond)
+	isWatcherReallyClosed(t, w)
+}
+
+func TestInotifyCloseSlightlyLaterWithWatch(t *testing.T) {
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	w, err := NewWatcher()
+	if err != nil {
+		t.Fatalf("Failed to create watcher")
+	}
+	w.Add(testDir)
+
+	// Wait until readEvents has reached unix.Read, and Close.
+	<-time.After(50 * time.Millisecond)
+	w.Close()
+
+	// Wait for the close to complete.
+	<-time.After(50 * time.Millisecond)
+	isWatcherReallyClosed(t, w)
+}
+
+func TestInotifyCloseAfterRead(t *testing.T) {
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	w, err := NewWatcher()
+	if err != nil {
+		t.Fatalf("Failed to create watcher")
+	}
+
+	err = w.Add(testDir)
+	if err != nil {
+		t.Fatalf("Failed to add .")
+	}
+
+	// Generate an event.
+	os.Create(filepath.Join(testDir, "somethingSOMETHINGsomethingSOMETHING"))
+
+	// Wait for readEvents to read the event, then close the watcher.
+	<-time.After(50 * time.Millisecond)
+	w.Close()
+
+	// Wait for the close to complete.
+	<-time.After(50 * time.Millisecond)
+	isWatcherReallyClosed(t, w)
+}
+
+func isWatcherReallyClosed(t *testing.T, w *Watcher) {
+	select {
+	case err, ok := <-w.Errors:
+		if ok {
+			t.Fatalf("w.Errors is not closed; readEvents is still alive after closing (error: %v)", err)
+		}
+	default:
+		t.Fatalf("w.Errors would have blocked; readEvents is still alive!")
+	}
+
+	select {
+	case _, ok := <-w.Events:
+		if ok {
+			t.Fatalf("w.Events is not closed; readEvents is still alive after closing")
+		}
+	default:
+		t.Fatalf("w.Events would have blocked; readEvents is still alive!")
+	}
+}
+
+func TestInotifyCloseCreate(t *testing.T) {
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	w, err := NewWatcher()
+	if err != nil {
+		t.Fatalf("Failed to create watcher: %v", err)
+	}
+	defer w.Close()
+
+	err = w.Add(testDir)
+	if err != nil {
+		t.Fatalf("Failed to add testDir: %v", err)
+	}
+	h, err := os.Create(filepath.Join(testDir, "testfile"))
+	if err != nil {
+		t.Fatalf("Failed to create file in testdir: %v", err)
+	}
+	h.Close()
+	select {
+	case _ = <-w.Events:
+	case err := <-w.Errors:
+		t.Fatalf("Error from watcher: %v", err)
+	case <-time.After(50 * time.Millisecond):
+		t.Fatalf("Took too long to wait for event")
+	}
+
+	// At this point, we've received one event, so the goroutine is ready.
+	// It's also blocking on unix.Read.
+	// Now we try to swap the file descriptor under its nose.
+	w.Close()
+	w, err = NewWatcher()
+	defer w.Close()
+	if err != nil {
+		t.Fatalf("Failed to create second watcher: %v", err)
+	}
+
+	<-time.After(50 * time.Millisecond)
+	err = w.Add(testDir)
+	if err != nil {
+		t.Fatalf("Error adding testDir again: %v", err)
+	}
+}
+
+func TestInotifyStress(t *testing.T) {
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+	testFile := filepath.Join(testDir, "testfile")
+
+	w, err := NewWatcher()
+	if err != nil {
+		t.Fatalf("Failed to create watcher: %v", err)
+	}
+	defer w.Close()
+
+	killchan := make(chan struct{})
+	defer close(killchan)
+
+	err = w.Add(testDir)
+	if err != nil {
+		t.Fatalf("Failed to add testDir: %v", err)
+	}
+
+	proc, err := os.FindProcess(os.Getpid())
+	if err != nil {
+		t.Fatalf("Error finding process: %v", err)
+	}
+
+	go func() {
+		for {
+			select {
+			case <-time.After(5 * time.Millisecond):
+				err := proc.Signal(unix.SIGUSR1)
+				if err != nil {
+					t.Fatalf("Signal failed: %v", err)
+				}
+			case <-killchan:
+				return
+			}
+		}
+	}()
+
+	go func() {
+		for {
+			select {
+			case <-time.After(11 * time.Millisecond):
+				err := w.poller.wake()
+				if err != nil {
+					t.Fatalf("Wake failed: %v", err)
+				}
+			case <-killchan:
+				return
+			}
+		}
+	}()
+
+	go func() {
+		for {
+			select {
+			case <-killchan:
+				return
+			default:
+				handle, err := os.Create(testFile)
+				if err != nil {
+					t.Fatalf("Create failed: %v", err)
+				}
+				handle.Close()
+				time.Sleep(time.Millisecond)
+				err = os.Remove(testFile)
+				if err != nil {
+					t.Fatalf("Remove failed: %v", err)
+				}
+			}
+		}
+	}()
+
+	creates := 0
+	removes := 0
+	after := time.After(5 * time.Second)
+	for {
+		select {
+		case <-after:
+			if creates-removes > 1 || creates-removes < -1 {
+				t.Fatalf("Creates and removes should not be off by more than one: %d creates, %d removes", creates, removes)
+			}
+			if creates < 50 {
+				t.Fatalf("Expected at least 50 creates, got %d", creates)
+			}
+			return
+		case err := <-w.Errors:
+			t.Fatalf("Got an error from watcher: %v", err)
+		case evt := <-w.Events:
+			if evt.Name != testFile {
+				t.Fatalf("Got an event for an unknown file: %s", evt.Name)
+			}
+			if evt.Op == Create {
+				creates++
+			}
+			if evt.Op == Remove {
+				removes++
+			}
+		}
+	}
+}
+
+func TestInotifyRemoveTwice(t *testing.T) {
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+	testFile := filepath.Join(testDir, "testfile")
+
+	handle, err := os.Create(testFile)
+	if err != nil {
+		t.Fatalf("Create failed: %v", err)
+	}
+	handle.Close()
+
+	w, err := NewWatcher()
+	if err != nil {
+		t.Fatalf("Failed to create watcher: %v", err)
+	}
+	defer w.Close()
+
+	err = w.Add(testFile)
+	if err != nil {
+		t.Fatalf("Failed to add testFile: %v", err)
+	}
+
+	err = os.Remove(testFile)
+	if err != nil {
+		t.Fatalf("Failed to remove testFile: %v", err)
+	}
+
+	err = w.Remove(testFile)
+	if err == nil {
+		t.Fatalf("no error on removing invalid file")
+	}
+	s1 := fmt.Sprintf("%s", err)
+
+	err = w.Remove(testFile)
+	if err == nil {
+		t.Fatalf("no error on removing invalid file")
+	}
+	s2 := fmt.Sprintf("%s", err)
+
+	if s1 != s2 {
+		t.Fatalf("receive different error - %s / %s", s1, s2)
+	}
+}
+
+func TestInotifyInnerMapLength(t *testing.T) {
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+	testFile := filepath.Join(testDir, "testfile")
+
+	handle, err := os.Create(testFile)
+	if err != nil {
+		t.Fatalf("Create failed: %v", err)
+	}
+	handle.Close()
+
+	w, err := NewWatcher()
+	if err != nil {
+		t.Fatalf("Failed to create watcher: %v", err)
+	}
+	defer w.Close()
+
+	err = w.Add(testFile)
+	if err != nil {
+		t.Fatalf("Failed to add testFile: %v", err)
+	}
+	go func() {
+		for err := range w.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	err = os.Remove(testFile)
+	if err != nil {
+		t.Fatalf("Failed to remove testFile: %v", err)
+	}
+	_ = <-w.Events                      // consume Remove event
+	<-time.After(50 * time.Millisecond) // wait IN_IGNORE propagated
+
+	w.mu.Lock()
+	defer w.mu.Unlock()
+	if len(w.watches) != 0 {
+		t.Fatalf("Expected watches len is 0, but got: %d, %v", len(w.watches), w.watches)
+	}
+	if len(w.paths) != 0 {
+		t.Fatalf("Expected paths len is 0, but got: %d, %v", len(w.paths), w.paths)
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/integration_darwin_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/integration_darwin_test.go b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/integration_darwin_test.go
new file mode 100644
index 0000000..5564554
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/integration_darwin_test.go
@@ -0,0 +1,147 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fsnotify
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+	"time"
+
+	"golang.org/x/sys/unix"
+)
+
+// testExchangedataForWatcher tests the watcher with the exchangedata operation on OS X.
+//
+// This is widely used for atomic saves on OS X, e.g. TextMate and in Apple's NSDocument.
+//
+// See https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html
+// Also see: https://github.com/textmate/textmate/blob/cd016be29489eba5f3c09b7b70b06da134dda550/Frameworks/io/src/swap_file_data.cc#L20
+func testExchangedataForWatcher(t *testing.T, watchDir bool) {
+	// Create directory to watch
+	testDir1 := tempMkdir(t)
+
+	// For the intermediate file
+	testDir2 := tempMkdir(t)
+
+	defer os.RemoveAll(testDir1)
+	defer os.RemoveAll(testDir2)
+
+	resolvedFilename := "TestFsnotifyEvents.file"
+
+	// TextMate does:
+	//
+	// 1. exchangedata (intermediate, resolved)
+	// 2. unlink intermediate
+	//
+	// Let's try to simulate that:
+	resolved := filepath.Join(testDir1, resolvedFilename)
+	intermediate := filepath.Join(testDir2, resolvedFilename+"~")
+
+	// Make sure we create the file before we start watching
+	createAndSyncFile(t, resolved)
+
+	watcher := newWatcher(t)
+
+	// Test both variants in isolation
+	if watchDir {
+		addWatch(t, watcher, testDir1)
+	} else {
+		addWatch(t, watcher, resolved)
+	}
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var removeReceived counter
+	var createReceived counter
+
+	done := make(chan bool)
+
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(resolved) {
+				if event.Op&Remove == Remove {
+					removeReceived.increment()
+				}
+				if event.Op&Create == Create {
+					createReceived.increment()
+				}
+			}
+			t.Logf("event received: %s", event)
+		}
+		done <- true
+	}()
+
+	// Repeat to make sure the watched file/directory "survives" the REMOVE/CREATE loop.
+	for i := 1; i <= 3; i++ {
+		// The intermediate file is created in a folder outside the watcher
+		createAndSyncFile(t, intermediate)
+
+		// 1. Swap
+		if err := unix.Exchangedata(intermediate, resolved, 0); err != nil {
+			t.Fatalf("[%d] exchangedata failed: %s", i, err)
+		}
+
+		time.Sleep(50 * time.Millisecond)
+
+		// 2. Delete the intermediate file
+		err := os.Remove(intermediate)
+
+		if err != nil {
+			t.Fatalf("[%d] remove %s failed: %s", i, intermediate, err)
+		}
+
+		time.Sleep(50 * time.Millisecond)
+
+	}
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+
+	// The events will be (CHMOD + REMOVE + CREATE) X 2. Let's focus on the last two:
+	if removeReceived.value() < 3 {
+		t.Fatal("fsnotify remove events have not been received after 500 ms")
+	}
+
+	if createReceived.value() < 3 {
+		t.Fatal("fsnotify create events have not been received after 500 ms")
+	}
+
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(2 * time.Second):
+		t.Fatal("event stream was not closed after 2 seconds")
+	}
+}
+
+// TestExchangedataInWatchedDir test exchangedata operation on file in watched dir.
+func TestExchangedataInWatchedDir(t *testing.T) {
+	testExchangedataForWatcher(t, true)
+}
+
+// TestExchangedataInWatchedDir test exchangedata operation on watched file.
+func TestExchangedataInWatchedFile(t *testing.T) {
+	testExchangedataForWatcher(t, false)
+}
+
+func createAndSyncFile(t *testing.T, filepath string) {
+	f1, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating %s failed: %s", filepath, err)
+	}
+	f1.Sync()
+	f1.Close()
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/integration_test.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/integration_test.go b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/integration_test.go
new file mode 100644
index 0000000..8b7e9d3
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/integration_test.go
@@ -0,0 +1,1237 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !plan9,!solaris
+
+package fsnotify
+
+import (
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"runtime"
+	"sync/atomic"
+	"testing"
+	"time"
+)
+
+// An atomic counter
+type counter struct {
+	val int32
+}
+
+func (c *counter) increment() {
+	atomic.AddInt32(&c.val, 1)
+}
+
+func (c *counter) value() int32 {
+	return atomic.LoadInt32(&c.val)
+}
+
+func (c *counter) reset() {
+	atomic.StoreInt32(&c.val, 0)
+}
+
+// tempMkdir makes a temporary directory
+func tempMkdir(t *testing.T) string {
+	dir, err := ioutil.TempDir("", "fsnotify")
+	if err != nil {
+		t.Fatalf("failed to create test directory: %s", err)
+	}
+	return dir
+}
+
+// tempMkFile makes a temporary file.
+func tempMkFile(t *testing.T, dir string) string {
+	f, err := ioutil.TempFile(dir, "fsnotify")
+	if err != nil {
+		t.Fatalf("failed to create test file: %v", err)
+	}
+	defer f.Close()
+	return f.Name()
+}
+
+// newWatcher initializes an fsnotify Watcher instance.
+func newWatcher(t *testing.T) *Watcher {
+	watcher, err := NewWatcher()
+	if err != nil {
+		t.Fatalf("NewWatcher() failed: %s", err)
+	}
+	return watcher
+}
+
+// addWatch adds a watch for a directory
+func addWatch(t *testing.T, watcher *Watcher, dir string) {
+	if err := watcher.Add(dir); err != nil {
+		t.Fatalf("watcher.Add(%q) failed: %s", dir, err)
+	}
+}
+
+func TestFsnotifyMultipleOperations(t *testing.T) {
+	watcher := newWatcher(t)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Create directory that's not watched
+	testDirToMoveFiles := tempMkdir(t)
+	defer os.RemoveAll(testDirToMoveFiles)
+
+	testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile")
+	testFileRenamed := filepath.Join(testDirToMoveFiles, "TestFsnotifySeqRename.testfile")
+
+	addWatch(t, watcher, testDir)
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var createReceived, modifyReceived, deleteReceived, renameReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
+				t.Logf("event received: %s", event)
+				if event.Op&Remove == Remove {
+					deleteReceived.increment()
+				}
+				if event.Op&Write == Write {
+					modifyReceived.increment()
+				}
+				if event.Op&Create == Create {
+					createReceived.increment()
+				}
+				if event.Op&Rename == Rename {
+					renameReceived.increment()
+				}
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	// Create a file
+	// This should add at least one event to the fsnotify event queue
+	var f *os.File
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+
+	time.Sleep(time.Millisecond)
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	if err := testRename(testFile, testFileRenamed); err != nil {
+		t.Fatalf("rename failed: %s", err)
+	}
+
+	// Modify the file outside of the watched dir
+	f, err = os.Open(testFileRenamed)
+	if err != nil {
+		t.Fatalf("open test renamed file failed: %s", err)
+	}
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	// Recreate the file that was moved
+	f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Close()
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	cReceived := createReceived.value()
+	if cReceived != 2 {
+		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
+	}
+	mReceived := modifyReceived.value()
+	if mReceived != 1 {
+		t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1)
+	}
+	dReceived := deleteReceived.value()
+	rReceived := renameReceived.value()
+	if dReceived+rReceived != 1 {
+		t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", rReceived+dReceived, 1)
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(2 * time.Second):
+		t.Fatal("event stream was not closed after 2 seconds")
+	}
+}
+
+func TestFsnotifyMultipleCreates(t *testing.T) {
+	watcher := newWatcher(t)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile")
+
+	addWatch(t, watcher, testDir)
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var createReceived, modifyReceived, deleteReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
+				t.Logf("event received: %s", event)
+				if event.Op&Remove == Remove {
+					deleteReceived.increment()
+				}
+				if event.Op&Create == Create {
+					createReceived.increment()
+				}
+				if event.Op&Write == Write {
+					modifyReceived.increment()
+				}
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	// Create a file
+	// This should add at least one event to the fsnotify event queue
+	var f *os.File
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+
+	time.Sleep(time.Millisecond)
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	os.Remove(testFile)
+
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	// Recreate the file
+	f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Close()
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	// Modify
+	f, err = os.OpenFile(testFile, os.O_WRONLY, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+
+	time.Sleep(time.Millisecond)
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	// Modify
+	f, err = os.OpenFile(testFile, os.O_WRONLY, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+
+	time.Sleep(time.Millisecond)
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	cReceived := createReceived.value()
+	if cReceived != 2 {
+		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
+	}
+	mReceived := modifyReceived.value()
+	if mReceived < 3 {
+		t.Fatalf("incorrect number of modify events received after 500 ms (%d vs atleast %d)", mReceived, 3)
+	}
+	dReceived := deleteReceived.value()
+	if dReceived != 1 {
+		t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", dReceived, 1)
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(2 * time.Second):
+		t.Fatal("event stream was not closed after 2 seconds")
+	}
+}
+
+func TestFsnotifyDirOnly(t *testing.T) {
+	watcher := newWatcher(t)
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Create a file before watching directory
+	// This should NOT add any events to the fsnotify event queue
+	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
+	{
+		var f *os.File
+		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
+		if err != nil {
+			t.Fatalf("creating test file failed: %s", err)
+		}
+		f.Sync()
+		f.Close()
+	}
+
+	addWatch(t, watcher, testDir)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	testFile := filepath.Join(testDir, "TestFsnotifyDirOnly.testfile")
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var createReceived, modifyReceived, deleteReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileAlreadyExists) {
+				t.Logf("event received: %s", event)
+				if event.Op&Remove == Remove {
+					deleteReceived.increment()
+				}
+				if event.Op&Write == Write {
+					modifyReceived.increment()
+				}
+				if event.Op&Create == Create {
+					createReceived.increment()
+				}
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	// Create a file
+	// This should add at least one event to the fsnotify event queue
+	var f *os.File
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+
+	time.Sleep(time.Millisecond)
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	os.Remove(testFile)
+	os.Remove(testFileAlreadyExists)
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	cReceived := createReceived.value()
+	if cReceived != 1 {
+		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 1)
+	}
+	mReceived := modifyReceived.value()
+	if mReceived != 1 {
+		t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1)
+	}
+	dReceived := deleteReceived.value()
+	if dReceived != 2 {
+		t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2)
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(2 * time.Second):
+		t.Fatal("event stream was not closed after 2 seconds")
+	}
+}
+
+func TestFsnotifyDeleteWatchedDir(t *testing.T) {
+	watcher := newWatcher(t)
+	defer watcher.Close()
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Create a file before watching directory
+	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
+	{
+		var f *os.File
+		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
+		if err != nil {
+			t.Fatalf("creating test file failed: %s", err)
+		}
+		f.Sync()
+		f.Close()
+	}
+
+	addWatch(t, watcher, testDir)
+
+	// Add a watch for testFile
+	addWatch(t, watcher, testFileAlreadyExists)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var deleteReceived counter
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFileAlreadyExists) {
+				t.Logf("event received: %s", event)
+				if event.Op&Remove == Remove {
+					deleteReceived.increment()
+				}
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+	}()
+
+	os.RemoveAll(testDir)
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	dReceived := deleteReceived.value()
+	if dReceived < 2 {
+		t.Fatalf("did not receive at least %d delete events, received %d after 500 ms", 2, dReceived)
+	}
+}
+
+func TestFsnotifySubDir(t *testing.T) {
+	watcher := newWatcher(t)
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	testFile1 := filepath.Join(testDir, "TestFsnotifyFile1.testfile")
+	testSubDir := filepath.Join(testDir, "sub")
+	testSubDirFile := filepath.Join(testDir, "sub/TestFsnotifyFile1.testfile")
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var createReceived, deleteReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testSubDir) || event.Name == filepath.Clean(testFile1) {
+				t.Logf("event received: %s", event)
+				if event.Op&Create == Create {
+					createReceived.increment()
+				}
+				if event.Op&Remove == Remove {
+					deleteReceived.increment()
+				}
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	addWatch(t, watcher, testDir)
+
+	// Create sub-directory
+	if err := os.Mkdir(testSubDir, 0777); err != nil {
+		t.Fatalf("failed to create test sub-directory: %s", err)
+	}
+
+	// Create a file
+	var f *os.File
+	f, err := os.OpenFile(testFile1, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+	f.Close()
+
+	// Create a file (Should not see this! we are not watching subdir)
+	var fs *os.File
+	fs, err = os.OpenFile(testSubDirFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	fs.Sync()
+	fs.Close()
+
+	time.Sleep(200 * time.Millisecond)
+
+	// Make sure receive deletes for both file and sub-directory
+	os.RemoveAll(testSubDir)
+	os.Remove(testFile1)
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	cReceived := createReceived.value()
+	if cReceived != 2 {
+		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
+	}
+	dReceived := deleteReceived.value()
+	if dReceived != 2 {
+		t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2)
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(2 * time.Second):
+		t.Fatal("event stream was not closed after 2 seconds")
+	}
+}
+
+func TestFsnotifyRename(t *testing.T) {
+	watcher := newWatcher(t)
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	addWatch(t, watcher, testDir)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	testFile := filepath.Join(testDir, "TestFsnotifyEvents.testfile")
+	testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var renameReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) {
+				if event.Op&Rename == Rename {
+					renameReceived.increment()
+				}
+				t.Logf("event received: %s", event)
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	// Create a file
+	// This should add at least one event to the fsnotify event queue
+	var f *os.File
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	// Add a watch for testFile
+	addWatch(t, watcher, testFile)
+
+	if err := testRename(testFile, testFileRenamed); err != nil {
+		t.Fatalf("rename failed: %s", err)
+	}
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	if renameReceived.value() == 0 {
+		t.Fatal("fsnotify rename events have not been received after 500 ms")
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(2 * time.Second):
+		t.Fatal("event stream was not closed after 2 seconds")
+	}
+
+	os.Remove(testFileRenamed)
+}
+
+func TestFsnotifyRenameToCreate(t *testing.T) {
+	watcher := newWatcher(t)
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Create directory to get file
+	testDirFrom := tempMkdir(t)
+	defer os.RemoveAll(testDirFrom)
+
+	addWatch(t, watcher, testDir)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile")
+	testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var createReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) {
+				if event.Op&Create == Create {
+					createReceived.increment()
+				}
+				t.Logf("event received: %s", event)
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	// Create a file
+	// This should add at least one event to the fsnotify event queue
+	var f *os.File
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+	f.Close()
+
+	if err := testRename(testFile, testFileRenamed); err != nil {
+		t.Fatalf("rename failed: %s", err)
+	}
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	if createReceived.value() == 0 {
+		t.Fatal("fsnotify create events have not been received after 500 ms")
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(2 * time.Second):
+		t.Fatal("event stream was not closed after 2 seconds")
+	}
+
+	os.Remove(testFileRenamed)
+}
+
+func TestFsnotifyRenameToOverwrite(t *testing.T) {
+	switch runtime.GOOS {
+	case "plan9", "windows":
+		t.Skipf("skipping test on %q (os.Rename over existing file does not create event).", runtime.GOOS)
+	}
+
+	watcher := newWatcher(t)
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Create directory to get file
+	testDirFrom := tempMkdir(t)
+	defer os.RemoveAll(testDirFrom)
+
+	testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile")
+	testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
+
+	// Create a file
+	var fr *os.File
+	fr, err := os.OpenFile(testFileRenamed, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	fr.Sync()
+	fr.Close()
+
+	addWatch(t, watcher, testDir)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var eventReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testFileRenamed) {
+				eventReceived.increment()
+				t.Logf("event received: %s", event)
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	// Create a file
+	// This should add at least one event to the fsnotify event queue
+	var f *os.File
+	f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+	f.Close()
+
+	if err := testRename(testFile, testFileRenamed); err != nil {
+		t.Fatalf("rename failed: %s", err)
+	}
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	if eventReceived.value() == 0 {
+		t.Fatal("fsnotify events have not been received after 500 ms")
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(2 * time.Second):
+		t.Fatal("event stream was not closed after 2 seconds")
+	}
+
+	os.Remove(testFileRenamed)
+}
+
+func TestRemovalOfWatch(t *testing.T) {
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Create a file before watching directory
+	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
+	{
+		var f *os.File
+		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
+		if err != nil {
+			t.Fatalf("creating test file failed: %s", err)
+		}
+		f.Sync()
+		f.Close()
+	}
+
+	watcher := newWatcher(t)
+	defer watcher.Close()
+
+	addWatch(t, watcher, testDir)
+	if err := watcher.Remove(testDir); err != nil {
+		t.Fatalf("Could not remove the watch: %v\n", err)
+	}
+
+	go func() {
+		select {
+		case ev := <-watcher.Events:
+			t.Fatalf("We received event: %v\n", ev)
+		case <-time.After(500 * time.Millisecond):
+			t.Log("No event received, as expected.")
+		}
+	}()
+
+	time.Sleep(200 * time.Millisecond)
+	// Modify the file outside of the watched dir
+	f, err := os.Open(testFileAlreadyExists)
+	if err != nil {
+		t.Fatalf("Open test file failed: %s", err)
+	}
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+	if err := os.Chmod(testFileAlreadyExists, 0700); err != nil {
+		t.Fatalf("chmod failed: %s", err)
+	}
+	time.Sleep(400 * time.Millisecond)
+}
+
+func TestFsnotifyAttrib(t *testing.T) {
+	if runtime.GOOS == "windows" {
+		t.Skip("attributes don't work on Windows.")
+	}
+
+	watcher := newWatcher(t)
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	testFile := filepath.Join(testDir, "TestFsnotifyAttrib.testfile")
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	// The modifyReceived counter counts IsModify events that are not IsAttrib,
+	// and the attribReceived counts IsAttrib events (which are also IsModify as
+	// a consequence).
+	var modifyReceived counter
+	var attribReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
+				if event.Op&Write == Write {
+					modifyReceived.increment()
+				}
+				if event.Op&Chmod == Chmod {
+					attribReceived.increment()
+				}
+				t.Logf("event received: %s", event)
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	// Create a file
+	// This should add at least one event to the fsnotify event queue
+	var f *os.File
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	// Add a watch for testFile
+	addWatch(t, watcher, testFile)
+
+	if err := os.Chmod(testFile, 0700); err != nil {
+		t.Fatalf("chmod failed: %s", err)
+	}
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	// Creating/writing a file changes also the mtime, so IsAttrib should be set to true here
+	time.Sleep(500 * time.Millisecond)
+	if modifyReceived.value() != 0 {
+		t.Fatal("received an unexpected modify event when creating a test file")
+	}
+	if attribReceived.value() == 0 {
+		t.Fatal("fsnotify attribute events have not received after 500 ms")
+	}
+
+	// Modifying the contents of the file does not set the attrib flag (although eg. the mtime
+	// might have been modified).
+	modifyReceived.reset()
+	attribReceived.reset()
+
+	f, err = os.OpenFile(testFile, os.O_WRONLY, 0)
+	if err != nil {
+		t.Fatalf("reopening test file failed: %s", err)
+	}
+
+	f.WriteString("more data")
+	f.Sync()
+	f.Close()
+
+	time.Sleep(500 * time.Millisecond)
+
+	if modifyReceived.value() != 1 {
+		t.Fatal("didn't receive a modify event after changing test file contents")
+	}
+
+	if attribReceived.value() != 0 {
+		t.Fatal("did receive an unexpected attrib event after changing test file contents")
+	}
+
+	modifyReceived.reset()
+	attribReceived.reset()
+
+	// Doing a chmod on the file should trigger an event with the "attrib" flag set (the contents
+	// of the file are not changed though)
+	if err := os.Chmod(testFile, 0600); err != nil {
+		t.Fatalf("chmod failed: %s", err)
+	}
+
+	time.Sleep(500 * time.Millisecond)
+
+	if attribReceived.value() != 1 {
+		t.Fatal("didn't receive an attribute change after 500ms")
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(1e9):
+		t.Fatal("event stream was not closed after 1 second")
+	}
+
+	os.Remove(testFile)
+}
+
+func TestFsnotifyClose(t *testing.T) {
+	watcher := newWatcher(t)
+	watcher.Close()
+
+	var done int32
+	go func() {
+		watcher.Close()
+		atomic.StoreInt32(&done, 1)
+	}()
+
+	time.Sleep(50e6) // 50 ms
+	if atomic.LoadInt32(&done) == 0 {
+		t.Fatal("double Close() test failed: second Close() call didn't return")
+	}
+
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	if err := watcher.Add(testDir); err == nil {
+		t.Fatal("expected error on Watch() after Close(), got nil")
+	}
+}
+
+func TestFsnotifyFakeSymlink(t *testing.T) {
+	if runtime.GOOS == "windows" {
+		t.Skip("symlinks don't work on Windows.")
+	}
+
+	watcher := newWatcher(t)
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	var errorsReceived counter
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for errors := range watcher.Errors {
+			t.Logf("Received error: %s", errors)
+			errorsReceived.increment()
+		}
+	}()
+
+	// Count the CREATE events received
+	var createEventsReceived, otherEventsReceived counter
+	go func() {
+		for ev := range watcher.Events {
+			t.Logf("event received: %s", ev)
+			if ev.Op&Create == Create {
+				createEventsReceived.increment()
+			} else {
+				otherEventsReceived.increment()
+			}
+		}
+	}()
+
+	addWatch(t, watcher, testDir)
+
+	if err := os.Symlink(filepath.Join(testDir, "zzz"), filepath.Join(testDir, "zzznew")); err != nil {
+		t.Fatalf("Failed to create bogus symlink: %s", err)
+	}
+	t.Logf("Created bogus symlink")
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+
+	// Should not be error, just no events for broken links (watching nothing)
+	if errorsReceived.value() > 0 {
+		t.Fatal("fsnotify errors have been received.")
+	}
+	if otherEventsReceived.value() > 0 {
+		t.Fatal("fsnotify other events received on the broken link")
+	}
+
+	// Except for 1 create event (for the link itself)
+	if createEventsReceived.value() == 0 {
+		t.Fatal("fsnotify create events were not received after 500 ms")
+	}
+	if createEventsReceived.value() > 1 {
+		t.Fatal("fsnotify more create events received than expected")
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+}
+
+func TestCyclicSymlink(t *testing.T) {
+	if runtime.GOOS == "windows" {
+		t.Skip("symlinks don't work on Windows.")
+	}
+
+	watcher := newWatcher(t)
+
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	link := path.Join(testDir, "link")
+	if err := os.Symlink(".", link); err != nil {
+		t.Fatalf("could not make symlink: %v", err)
+	}
+	addWatch(t, watcher, testDir)
+
+	var createEventsReceived counter
+	go func() {
+		for ev := range watcher.Events {
+			if ev.Op&Create == Create {
+				createEventsReceived.increment()
+			}
+		}
+	}()
+
+	if err := os.Remove(link); err != nil {
+		t.Fatalf("Error removing link: %v", err)
+	}
+
+	// It would be nice to be able to expect a delete event here, but kqueue has
+	// no way for us to get events on symlinks themselves, because opening them
+	// opens an fd to the file to which they point.
+
+	if err := ioutil.WriteFile(link, []byte("foo"), 0700); err != nil {
+		t.Fatalf("could not make symlink: %v", err)
+	}
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+
+	if got := createEventsReceived.value(); got == 0 {
+		t.Errorf("want at least 1 create event got %v", got)
+	}
+
+	watcher.Close()
+}
+
+// TestConcurrentRemovalOfWatch tests that concurrent calls to RemoveWatch do not race.
+// See https://codereview.appspot.com/103300045/
+// go test -test.run=TestConcurrentRemovalOfWatch -test.cpu=1,1,1,1,1 -race
+func TestConcurrentRemovalOfWatch(t *testing.T) {
+	if runtime.GOOS != "darwin" {
+		t.Skip("regression test for race only present on darwin")
+	}
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Create a file before watching directory
+	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
+	{
+		var f *os.File
+		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
+		if err != nil {
+			t.Fatalf("creating test file failed: %s", err)
+		}
+		f.Sync()
+		f.Close()
+	}
+
+	watcher := newWatcher(t)
+	defer watcher.Close()
+
+	addWatch(t, watcher, testDir)
+
+	// Test that RemoveWatch can be invoked concurrently, with no data races.
+	removed1 := make(chan struct{})
+	go func() {
+		defer close(removed1)
+		watcher.Remove(testDir)
+	}()
+	removed2 := make(chan struct{})
+	go func() {
+		close(removed2)
+		watcher.Remove(testDir)
+	}()
+	<-removed1
+	<-removed2
+}
+
+func TestClose(t *testing.T) {
+	// Regression test for #59 bad file descriptor from Close
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	watcher := newWatcher(t)
+	if err := watcher.Add(testDir); err != nil {
+		t.Fatalf("Expected no error on Add, got %v", err)
+	}
+	err := watcher.Close()
+	if err != nil {
+		t.Fatalf("Expected no error on Close, got %v.", err)
+	}
+}
+
+// TestRemoveWithClose tests if one can handle Remove events and, at the same
+// time, close Watcher object without any data races.
+func TestRemoveWithClose(t *testing.T) {
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	const fileN = 200
+	tempFiles := make([]string, 0, fileN)
+	for i := 0; i < fileN; i++ {
+		tempFiles = append(tempFiles, tempMkFile(t, testDir))
+	}
+	watcher := newWatcher(t)
+	if err := watcher.Add(testDir); err != nil {
+		t.Fatalf("Expected no error on Add, got %v", err)
+	}
+	startC, stopC := make(chan struct{}), make(chan struct{})
+	errC := make(chan error)
+	go func() {
+		for {
+			select {
+			case <-watcher.Errors:
+			case <-watcher.Events:
+			case <-stopC:
+				return
+			}
+		}
+	}()
+	go func() {
+		<-startC
+		for _, fileName := range tempFiles {
+			os.Remove(fileName)
+		}
+	}()
+	go func() {
+		<-startC
+		errC <- watcher.Close()
+	}()
+	close(startC)
+	defer close(stopC)
+	if err := <-errC; err != nil {
+		t.Fatalf("Expected no error on Close, got %v.", err)
+	}
+}
+
+func testRename(file1, file2 string) error {
+	switch runtime.GOOS {
+	case "windows", "plan9":
+		return os.Rename(file1, file2)
+	default:
+		cmd := exec.Command("mv", file1, file2)
+		return cmd.Run()
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/kqueue.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/kqueue.go b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/kqueue.go
new file mode 100644
index 0000000..c2b4acb
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/kqueue.go
@@ -0,0 +1,503 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build freebsd openbsd netbsd dragonfly darwin
+
+package fsnotify
+
+import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"sync"
+	"time"
+
+	"golang.org/x/sys/unix"
+)
+
+// Watcher watches a set of files, delivering events to a channel.
+type Watcher struct {
+	Events chan Event
+	Errors chan error
+	done   chan bool // Channel for sending a "quit message" to the reader goroutine
+
+	kq int // File descriptor (as returned by the kqueue() syscall).
+
+	mu              sync.Mutex        // Protects access to watcher data
+	watches         map[string]int    // Map of watched file descriptors (key: path).
+	externalWatches map[string]bool   // Map of watches added by user of the library.
+	dirFlags        map[string]uint32 // Map of watched directories to fflags used in kqueue.
+	paths           map[int]pathInfo  // Map file descriptors to path names for processing kqueue events.
+	fileExists      map[string]bool   // Keep track of if we know this file exists (to stop duplicate create events).
+	isClosed        bool              // Set to true when Close() is first called
+}
+
+type pathInfo struct {
+	name  string
+	isDir bool
+}
+
+// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
+func NewWatcher() (*Watcher, error) {
+	kq, err := kqueue()
+	if err != nil {
+		return nil, err
+	}
+
+	w := &Watcher{
+		kq:              kq,
+		watches:         make(map[string]int),
+		dirFlags:        make(map[string]uint32),
+		paths:           make(map[int]pathInfo),
+		fileExists:      make(map[string]bool),
+		externalWatches: make(map[string]bool),
+		Events:          make(chan Event),
+		Errors:          make(chan error),
+		done:            make(chan bool),
+	}
+
+	go w.readEvents()
+	return w, nil
+}
+
+// Close removes all watches and closes the events channel.
+func (w *Watcher) Close() error {
+	w.mu.Lock()
+	if w.isClosed {
+		w.mu.Unlock()
+		return nil
+	}
+	w.isClosed = true
+	w.mu.Unlock()
+
+	// copy paths to remove while locked
+	w.mu.Lock()
+	var pathsToRemove = make([]string, 0, len(w.watches))
+	for name := range w.watches {
+		pathsToRemove = append(pathsToRemove, name)
+	}
+	w.mu.Unlock()
+	// unlock before calling Remove, which also locks
+
+	var err error
+	for _, name := range pathsToRemove {
+		if e := w.Remove(name); e != nil && err == nil {
+			err = e
+		}
+	}
+
+	// Send "quit" message to the reader goroutine:
+	w.done <- true
+
+	return nil
+}
+
+// Add starts watching the named file or directory (non-recursively).
+func (w *Watcher) Add(name string) error {
+	w.mu.Lock()
+	w.externalWatches[name] = true
+	w.mu.Unlock()
+	_, err := w.addWatch(name, noteAllEvents)
+	return err
+}
+
+// Remove stops watching the the named file or directory (non-recursively).
+func (w *Watcher) Remove(name string) error {
+	name = filepath.Clean(name)
+	w.mu.Lock()
+	watchfd, ok := w.watches[name]
+	w.mu.Unlock()
+	if !ok {
+		return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
+	}
+
+	const registerRemove = unix.EV_DELETE
+	if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
+		return err
+	}
+
+	unix.Close(watchfd)
+
+	w.mu.Lock()
+	isDir := w.paths[watchfd].isDir
+	delete(w.watches, name)
+	delete(w.paths, watchfd)
+	delete(w.dirFlags, name)
+	w.mu.Unlock()
+
+	// Find all watched paths that are in this directory that are not external.
+	if isDir {
+		var pathsToRemove []string
+		w.mu.Lock()
+		for _, path := range w.paths {
+			wdir, _ := filepath.Split(path.name)
+			if filepath.Clean(wdir) == name {
+				if !w.externalWatches[path.name] {
+					pathsToRemove = append(pathsToRemove, path.name)
+				}
+			}
+		}
+		w.mu.Unlock()
+		for _, name := range pathsToRemove {
+			// Since these are internal, not much sense in propagating error
+			// to the user, as that will just confuse them with an error about
+			// a path they did not explicitly watch themselves.
+			w.Remove(name)
+		}
+	}
+
+	return nil
+}
+
+// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
+const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
+
+// keventWaitTime to block on each read from kevent
+var keventWaitTime = durationToTimespec(100 * time.Millisecond)
+
+// addWatch adds name to the watched file set.
+// The flags are interpreted as described in kevent(2).
+// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
+func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
+	var isDir bool
+	// Make ./name and name equivalent
+	name = filepath.Clean(name)
+
+	w.mu.Lock()
+	if w.isClosed {
+		w.mu.Unlock()
+		return "", errors.New("kevent instance already closed")
+	}
+	watchfd, alreadyWatching := w.watches[name]
+	// We already have a watch, but we can still override flags.
+	if alreadyWatching {
+		isDir = w.paths[watchfd].isDir
+	}
+	w.mu.Unlock()
+
+	if !alreadyWatching {
+		fi, err := os.Lstat(name)
+		if err != nil {
+			return "", err
+		}
+
+		// Don't watch sockets.
+		if fi.Mode()&os.ModeSocket == os.ModeSocket {
+			return "", nil
+		}
+
+		// Don't watch named pipes.
+		if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
+			return "", nil
+		}
+
+		// Follow Symlinks
+		// Unfortunately, Linux can add bogus symlinks to watch list without
+		// issue, and Windows can't do symlinks period (AFAIK). To  maintain
+		// consistency, we will act like everything is fine. There will simply
+		// be no file events for broken symlinks.
+		// Hence the returns of nil on errors.
+		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
+			name, err = filepath.EvalSymlinks(name)
+			if err != nil {
+				return "", nil
+			}
+
+			w.mu.Lock()
+			_, alreadyWatching = w.watches[name]
+			w.mu.Unlock()
+
+			if alreadyWatching {
+				return name, nil
+			}
+
+			fi, err = os.Lstat(name)
+			if err != nil {
+				return "", nil
+			}
+		}
+
+		watchfd, err = unix.Open(name, openMode, 0700)
+		if watchfd == -1 {
+			return "", err
+		}
+
+		isDir = fi.IsDir()
+	}
+
+	const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE
+	if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
+		unix.Close(watchfd)
+		return "", err
+	}
+
+	if !alreadyWatching {
+		w.mu.Lock()
+		w.watches[name] = watchfd
+		w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
+		w.mu.Unlock()
+	}
+
+	if isDir {
+		// Watch the directory if it has not been watched before,
+		// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
+		w.mu.Lock()
+
+		watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
+			(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
+		// Store flags so this watch can be updated later
+		w.dirFlags[name] = flags
+		w.mu.Unlock()
+
+		if watchDir {
+			if err := w.watchDirectoryFiles(name); err != nil {
+				return "", err
+			}
+		}
+	}
+	return name, nil
+}
+
+// readEvents reads from kqueue and converts the received kevents into
+// Event values that it sends down the Events channel.
+func (w *Watcher) readEvents() {
+	eventBuffer := make([]unix.Kevent_t, 10)
+
+	for {
+		// See if there is a message on the "done" channel
+		select {
+		case <-w.done:
+			err := unix.Close(w.kq)
+			if err != nil {
+				w.Errors <- err
+			}
+			close(w.Events)
+			close(w.Errors)
+			return
+		default:
+		}
+
+		// Get new events
+		kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
+		// EINTR is okay, the syscall was interrupted before timeout expired.
+		if err != nil && err != unix.EINTR {
+			w.Errors <- err
+			continue
+		}
+
+		// Flush the events we received to the Events channel
+		for len(kevents) > 0 {
+			kevent := &kevents[0]
+			watchfd := int(kevent.Ident)
+			mask := uint32(kevent.Fflags)
+			w.mu.Lock()
+			path := w.paths[watchfd]
+			w.mu.Unlock()
+			event := newEvent(path.name, mask)
+
+			if path.isDir && !(event.Op&Remove == Remove) {
+				// Double check to make sure the directory exists. This can happen when
+				// we do a rm -fr on a recursively watched folders and we receive a
+				// modification event first but the folder has been deleted and later
+				// receive the delete event
+				if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
+					// mark is as delete event
+					event.Op |= Remove
+				}
+			}
+
+			if event.Op&Rename == Rename || event.Op&Remove == Remove {
+				w.Remove(event.Name)
+				w.mu.Lock()
+				delete(w.fileExists, event.Name)
+				w.mu.Unlock()
+			}
+
+			if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
+				w.sendDirectoryChangeEvents(event.Name)
+			} else {
+				// Send the event on the Events channel
+				w.Events <- event
+			}
+
+			if event.Op&Remove == Remove {
+				// Look for a file that may have overwritten this.
+				// For example, mv f1 f2 will delete f2, then create f2.
+				if path.isDir {
+					fileDir := filepath.Clean(event.Name)
+					w.mu.Lock()
+					_, found := w.watches[fileDir]
+					w.mu.Unlock()
+					if found {
+						// make sure the directory exists before we watch for changes. When we
+						// do a recursive watch and perform rm -fr, the parent directory might
+						// have gone missing, ignore the missing directory and let the
+						// upcoming delete event remove the watch from the parent directory.
+						if _, err := os.Lstat(fileDir); err == nil {
+							w.sendDirectoryChangeEvents(fileDir)
+						}
+					}
+				} else {
+					filePath := filepath.Clean(event.Name)
+					if fileInfo, err := os.Lstat(filePath); err == nil {
+						w.sendFileCreatedEventIfNew(filePath, fileInfo)
+					}
+				}
+			}
+
+			// Move to next event
+			kevents = kevents[1:]
+		}
+	}
+}
+
+// newEvent returns an platform-independent Event based on kqueue Fflags.
+func newEvent(name string, mask uint32) Event {
+	e := Event{Name: name}
+	if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
+		e.Op |= Remove
+	}
+	if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
+		e.Op |= Write
+	}
+	if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
+		e.Op |= Rename
+	}
+	if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
+		e.Op |= Chmod
+	}
+	return e
+}
+
+func newCreateEvent(name string) Event {
+	return Event{Name: name, Op: Create}
+}
+
+// watchDirectoryFiles to mimic inotify when adding a watch on a directory
+func (w *Watcher) watchDirectoryFiles(dirPath string) error {
+	// Get all files
+	files, err := ioutil.ReadDir(dirPath)
+	if err != nil {
+		return err
+	}
+
+	for _, fileInfo := range files {
+		filePath := filepath.Join(dirPath, fileInfo.Name())
+		filePath, err = w.internalWatch(filePath, fileInfo)
+		if err != nil {
+			return err
+		}
+
+		w.mu.Lock()
+		w.fileExists[filePath] = true
+		w.mu.Unlock()
+	}
+
+	return nil
+}
+
+// sendDirectoryEvents searches the directory for newly created files
+// and sends them over the event channel. This functionality is to have
+// the BSD version of fsnotify match Linux inotify which provides a
+// create event for files created in a watched directory.
+func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
+	// Get all files
+	files, err := ioutil.ReadDir(dirPath)
+	if err != nil {
+		w.Errors <- err
+	}
+
+	// Search for new files
+	for _, fileInfo := range files {
+		filePath := filepath.Join(dirPath, fileInfo.Name())
+		err := w.sendFileCreatedEventIfNew(filePath, fileInfo)
+
+		if err != nil {
+			return
+		}
+	}
+}
+
+// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
+func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
+	w.mu.Lock()
+	_, doesExist := w.fileExists[filePath]
+	w.mu.Unlock()
+	if !doesExist {
+		// Send create event
+		w.Events <- newCreateEvent(filePath)
+	}
+
+	// like watchDirectoryFiles (but without doing another ReadDir)
+	filePath, err = w.internalWatch(filePath, fileInfo)
+	if err != nil {
+		return err
+	}
+
+	w.mu.Lock()
+	w.fileExists[filePath] = true
+	w.mu.Unlock()
+
+	return nil
+}
+
+func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
+	if fileInfo.IsDir() {
+		// mimic Linux providing delete events for subdirectories
+		// but preserve the flags used if currently watching subdirectory
+		w.mu.Lock()
+		flags := w.dirFlags[name]
+		w.mu.Unlock()
+
+		flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
+		return w.addWatch(name, flags)
+	}
+
+	// watch file to mimic Linux inotify
+	return w.addWatch(name, noteAllEvents)
+}
+
+// kqueue creates a new kernel event queue and returns a descriptor.
+func kqueue() (kq int, err error) {
+	kq, err = unix.Kqueue()
+	if kq == -1 {
+		return kq, err
+	}
+	return kq, nil
+}
+
+// register events with the queue
+func register(kq int, fds []int, flags int, fflags uint32) error {
+	changes := make([]unix.Kevent_t, len(fds))
+
+	for i, fd := range fds {
+		// SetKevent converts int to the platform-specific types:
+		unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
+		changes[i].Fflags = fflags
+	}
+
+	// register the events
+	success, err := unix.Kevent(kq, changes, nil, nil)
+	if success == -1 {
+		return err
+	}
+	return nil
+}
+
+// read retrieves pending events, or waits until an event occurs.
+// A timeout of nil blocks indefinitely, while 0 polls the queue.
+func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) {
+	n, err := unix.Kevent(kq, nil, events, timeout)
+	if err != nil {
+		return nil, err
+	}
+	return events[0:n], nil
+}
+
+// durationToTimespec prepares a timeout value
+func durationToTimespec(d time.Duration) unix.Timespec {
+	return unix.NsecToTimespec(d.Nanoseconds())
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/open_mode_bsd.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/open_mode_bsd.go b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/open_mode_bsd.go
new file mode 100644
index 0000000..7d8de14
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/open_mode_bsd.go
@@ -0,0 +1,11 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build freebsd openbsd netbsd dragonfly
+
+package fsnotify
+
+import "golang.org/x/sys/unix"
+
+const openMode = unix.O_NONBLOCK | unix.O_RDONLY

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/open_mode_darwin.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/open_mode_darwin.go b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/open_mode_darwin.go
new file mode 100644
index 0000000..9139e17
--- /dev/null
+++ b/traffic_monitor_golang/vendor/gopkg.in/fsnotify.v1/open_mode_darwin.go
@@ -0,0 +1,12 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin
+
+package fsnotify
+
+import "golang.org/x/sys/unix"
+
+// note: this constant is not defined on BSD
+const openMode = unix.O_EVTONLY


[18/19] incubator-trafficcontrol git commit: Fix TM2 DS match to not loop

Posted by ne...@apache.org.
Fix TM2 DS match to not loop

Fixes Traffic Monitor 2.0 delivery service matching from ATS astats
to be a direct hash lookup, instead of a looped string compare.
This is a massive CPU gain, profiling it appeared to be up to 30% of
the entire app's CPU usage.


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

Branch: refs/heads/master
Commit: a722d929bdd658bd22ec0fa1724d9f5762efe5ff
Parents: 129a47e
Author: Robert Butts <ro...@gmail.com>
Authored: Fri Jan 27 13:33:24 2017 -0700
Committer: Dave Neuman <ne...@apache.org>
Committed: Mon Jan 30 08:28:47 2017 -0700

----------------------------------------------------------------------
 traffic_monitor_golang/traffic_monitor/cache/cache.go    | 11 ++++++++---
 .../traffic_monitor/trafficopsdata/trafficopsdata.go     | 11 +++++------
 2 files changed, 13 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/a722d929/traffic_monitor_golang/traffic_monitor/cache/cache.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/cache/cache.go b/traffic_monitor_golang/traffic_monitor/cache/cache.go
index 46cd68f..7436d2b 100644
--- a/traffic_monitor_golang/traffic_monitor/cache/cache.go
+++ b/traffic_monitor_golang/traffic_monitor/cache/cache.go
@@ -421,17 +421,22 @@ func processStatPlugin(server enum.CacheName, stats map[enum.DeliveryServiceName
 }
 
 func processStatPluginRemapStats(server enum.CacheName, stats map[enum.DeliveryServiceName]dsdata.Stat, toData todata.TOData, stat string, statParts []string, value interface{}, timeReceived time.Time) (map[enum.DeliveryServiceName]dsdata.Stat, error) {
-	if len(statParts) < 2 {
+	if len(statParts) < 3 {
 		return stats, fmt.Errorf("stat has no remap_stats deliveryservice and name parts")
 	}
 
-	fqdn := strings.Join(statParts[:len(statParts)-1], ".")
+	// the FQDN is `subsubdomain`.`subdomain`.`domain`. For a HTTP delivery service, `subsubdomain` will be the cache hostname; for a DNS delivery service, it will be `edge`. Then, `subdomain` is the delivery service regex.
+	subsubdomain := statParts[0]
+	subdomain := statParts[1]
+	domain := strings.Join(statParts[2:len(statParts)-1], ".")
 
-	ds, ok := toData.DeliveryServiceRegexes.DeliveryService(fqdn)
+	ds, ok := toData.DeliveryServiceRegexes.DeliveryService(domain, subdomain, subsubdomain)
 	if !ok {
+		fqdn := fmt.Sprintf("%s.%s.%s", subsubdomain, subdomain, domain)
 		return stats, fmt.Errorf("ERROR no delivery service match for fqdn '%v' stat '%v'\n", fqdn, strings.Join(statParts, "."))
 	}
 	if ds == "" {
+		fqdn := fmt.Sprintf("%s.%s.%s", subsubdomain, subdomain, domain)
 		return stats, fmt.Errorf("ERROR EMPTY delivery service fqdn %v stat %v\n", fqdn, strings.Join(statParts, "."))
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/a722d929/traffic_monitor_golang/traffic_monitor/trafficopsdata/trafficopsdata.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/trafficopsdata/trafficopsdata.go b/traffic_monitor_golang/traffic_monitor/trafficopsdata/trafficopsdata.go
index 69c7af1..afc6106 100644
--- a/traffic_monitor_golang/traffic_monitor/trafficopsdata/trafficopsdata.go
+++ b/traffic_monitor_golang/traffic_monitor/trafficopsdata/trafficopsdata.go
@@ -43,14 +43,13 @@ type Regexes struct {
 }
 
 // DeliveryService returns the delivery service which matches the given fqdn, or false.
-func (d Regexes) DeliveryService(fqdn string) (enum.DeliveryServiceName, bool) {
-	if ds, ok := d.DirectMatches[fqdn]; ok {
+func (d Regexes) DeliveryService(domain, subdomain, subsubdomain string) (enum.DeliveryServiceName, bool) {
+	if ds, ok := d.DotStartSlashDotFooSlashDotDotStar[subdomain]; ok {
 		return ds, true
 	}
-	for matchStr, ds := range d.DotStartSlashDotFooSlashDotDotStar {
-		if strings.Contains(fqdn, "."+matchStr+".") {
-			return ds, true
-		}
+	fqdn := fmt.Sprintf("%s.%s.%s", subsubdomain, subdomain, domain)
+	if ds, ok := d.DirectMatches[fqdn]; ok {
+		return ds, true
 	}
 	for regex, ds := range d.RegexMatch {
 		if regex.MatchString(fqdn) {


[08/19] incubator-trafficcontrol git commit: Move TM2 to trafficcontrol/traffic_monitor_golang

Posted by ne...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/deliveryservice/stat.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/deliveryservice/stat.go b/traffic_monitor_golang/traffic_monitor/deliveryservice/stat.go
new file mode 100644
index 0000000..9efd2c9
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/deliveryservice/stat.go
@@ -0,0 +1,358 @@
+package deliveryservice
+
+/*
+ * 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"
+	"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"
+	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"
+	todata "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/trafficopsdata"
+	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
+)
+
+// TODO remove 'ds' and 'stat' from names
+
+func setStaticData(dsStats dsdata.Stats, dsServers map[enum.DeliveryServiceName][]enum.CacheName) dsdata.Stats {
+	for ds, stat := range dsStats.DeliveryService {
+		stat.CommonStats.CachesConfiguredNum.Value = int64(len(dsServers[ds]))
+		dsStats.DeliveryService[ds] = stat // TODO consider changing dsStats.DeliveryService[ds] to a pointer so this kind of thing isn't necessary; possibly more performant, as well
+	}
+	return dsStats
+}
+
+func addAvailableData(dsStats dsdata.Stats, crStates peer.Crstates, serverCachegroups map[enum.CacheName]enum.CacheGroupName, serverDs map[enum.CacheName][]enum.DeliveryServiceName, serverTypes map[enum.CacheName]enum.CacheType, precomputed map[enum.CacheName]cache.PrecomputedData) (dsdata.Stats, error) {
+	for cache, available := range crStates.Caches {
+		cacheGroup, ok := serverCachegroups[cache]
+		if !ok {
+			log.Infof("CreateStats not adding availability data for '%s': not found in Cachegroups\n", cache)
+			continue
+		}
+		deliveryServices, ok := serverDs[cache]
+		if !ok {
+			log.Infof("CreateStats not adding availability data for '%s': not found in DeliveryServices\n", cache)
+			continue
+		}
+		cacheType, ok := serverTypes[cache]
+		if !ok {
+			log.Infof("CreateStats not adding availability data for '%s': not found in Server Types\n", cache)
+			continue
+		}
+
+		for _, deliveryService := range deliveryServices {
+			if deliveryService == "" {
+				log.Errorln("EMPTY addAvailableData DS") // various bugs in other functions can cause this - this will help identify and debug them.
+				continue
+			}
+
+			stat, ok := dsStats.DeliveryService[deliveryService]
+			if !ok {
+				log.Infof("CreateStats not adding availability data for '%s': not found in Stats\n", cache)
+				continue // TODO log warning? Error?
+			}
+
+			if available.IsAvailable {
+				stat.CommonStats.IsAvailable.Value = true
+				stat.CommonStats.IsHealthy.Value = true
+				stat.CommonStats.CachesAvailableNum.Value++
+				cacheGroupStats := stat.CacheGroups[cacheGroup]
+				cacheGroupStats.IsAvailable.Value = true
+				stat.CacheGroups[cacheGroup] = cacheGroupStats
+				stat.TotalStats.IsAvailable.Value = true
+				typeStats := stat.Types[cacheType]
+				typeStats.IsAvailable.Value = true
+				stat.Types[cacheType] = typeStats
+			}
+
+			// TODO fix nested ifs
+			if pc, ok := precomputed[cache]; ok {
+				if pc.Reporting {
+					stat.CommonStats.CachesReporting[cache] = true
+				} else {
+					log.Debugf("no reporting %v %v\n", cache, deliveryService)
+				}
+			} else {
+				log.Debugf("no result for %v %v\n", cache, deliveryService)
+			}
+
+			dsStats.DeliveryService[deliveryService] = stat // TODO Necessary? Remove?
+		}
+	}
+
+	// TODO move to its own func?
+	for dsName, ds := range crStates.Deliveryservice {
+		stat, ok := dsStats.DeliveryService[dsName]
+		if !ok {
+			log.Warnf("CreateStats not adding disabledLocations for '%s': not found in Stats\n", dsName)
+			continue // TODO log warning? Error?
+		}
+
+		// TODO determine if a deep copy is necessary
+		stat.CommonStats.CachesDisabled = make([]string, len(ds.DisabledLocations), len(ds.DisabledLocations))
+		for i, v := range ds.DisabledLocations {
+			stat.CommonStats.CachesDisabled[i] = string(v)
+		}
+		dsStats.DeliveryService[dsName] = stat // TODO Necessary? Remove?
+	}
+
+	return dsStats, nil
+}
+
+func newLastDSStat() dsdata.LastDSStat {
+	return dsdata.LastDSStat{
+		CacheGroups: map[enum.CacheGroupName]dsdata.LastStatsData{},
+		Type:        map[enum.CacheType]dsdata.LastStatsData{},
+		Caches:      map[enum.CacheName]dsdata.LastStatsData{},
+	}
+}
+
+// BytesPerKilobit is the number of bytes in a kilobit.
+const BytesPerKilobit = 125
+
+func addLastStat(lastData dsdata.LastStatData, newStat int64, newStatTime time.Time) (dsdata.LastStatData, error) {
+	if newStat == lastData.Stat {
+		return lastData, nil
+	}
+
+	if newStat < lastData.Stat {
+		return lastData, fmt.Errorf("new stat '%d'@'%v' value less than last stat '%d'@'%v'", lastData.Stat, lastData.Time, newStat, newStatTime)
+	}
+
+	if newStatTime.Before(lastData.Time) {
+		return lastData, fmt.Errorf("new stat '%d'@'%v' time less than last stat '%d'@'%v'", lastData.Stat, lastData.Time, newStat, newStatTime)
+	}
+
+	if lastData.Stat != 0 {
+		lastData.PerSec = float64(newStat-lastData.Stat) / newStatTime.Sub(lastData.Time).Seconds()
+	}
+
+	lastData.Stat = newStat
+	lastData.Time = newStatTime
+	return lastData, nil
+}
+
+func addLastStats(lastData dsdata.LastStatsData, newStats dsdata.StatCacheStats, newStatsTime time.Time) (dsdata.LastStatsData, error) {
+	errs := []error{nil, nil, nil, nil, nil}
+	lastData.Bytes, errs[0] = addLastStat(lastData.Bytes, newStats.OutBytes.Value, newStatsTime)
+	lastData.Status2xx, errs[1] = addLastStat(lastData.Status2xx, newStats.Status2xx.Value, newStatsTime)
+	lastData.Status3xx, errs[2] = addLastStat(lastData.Status3xx, newStats.Status3xx.Value, newStatsTime)
+	lastData.Status4xx, errs[3] = addLastStat(lastData.Status4xx, newStats.Status4xx.Value, newStatsTime)
+	lastData.Status5xx, errs[4] = addLastStat(lastData.Status5xx, newStats.Status5xx.Value, newStatsTime)
+	return lastData, util.JoinErrors(errs)
+}
+
+func addLastStatsToStatCacheStats(s dsdata.StatCacheStats, l dsdata.LastStatsData) dsdata.StatCacheStats {
+	s.Kbps.Value = l.Bytes.PerSec / BytesPerKilobit
+	s.Tps2xx.Value = l.Status2xx.PerSec
+	s.Tps3xx.Value = l.Status3xx.PerSec
+	s.Tps4xx.Value = l.Status4xx.PerSec
+	s.Tps5xx.Value = l.Status5xx.PerSec
+	s.TpsTotal.Value = s.Tps2xx.Value + s.Tps3xx.Value + s.Tps4xx.Value + s.Tps5xx.Value
+	return s
+}
+
+// addLastDSStatTotals takes a LastDSStat with only raw `Caches` data, and calculates and sets the `CacheGroups`, `Type`, and `Total` data, and returns the augmented structure.
+func addLastDSStatTotals(lastStat dsdata.LastDSStat, cachesReporting map[enum.CacheName]bool, serverCachegroups map[enum.CacheName]enum.CacheGroupName, serverTypes map[enum.CacheName]enum.CacheType) dsdata.LastDSStat {
+	cacheGroups := map[enum.CacheGroupName]dsdata.LastStatsData{}
+	cacheTypes := map[enum.CacheType]dsdata.LastStatsData{}
+	total := dsdata.LastStatsData{}
+	for cacheName, cacheStats := range lastStat.Caches {
+		if !cachesReporting[cacheName] {
+			continue
+		}
+
+		if cacheGroup, ok := serverCachegroups[cacheName]; ok {
+			cacheGroups[cacheGroup] = cacheGroups[cacheGroup].Sum(cacheStats)
+		} else {
+			log.Warnf("while computing delivery service data, cache %v not in cachegroups\n", cacheName)
+		}
+
+		if cacheType, ok := serverTypes[cacheName]; ok {
+			cacheTypes[cacheType] = cacheTypes[cacheType].Sum(cacheStats)
+		} else {
+			log.Warnf("while computing delivery service data, cache %v not in types\n", cacheName)
+		}
+		total = total.Sum(cacheStats)
+	}
+	lastStat.CacheGroups = cacheGroups
+	lastStat.Type = cacheTypes
+	lastStat.Total = total
+	return lastStat
+}
+
+// addDSPerSecStats calculates and adds the per-second delivery service stats to both the Stats and LastStats structures, and returns the augmented structures.
+func addDSPerSecStats(dsName enum.DeliveryServiceName, stat dsdata.Stat, lastStats dsdata.LastStats, dsStats dsdata.Stats, dsStatsTime time.Time, serverCachegroups map[enum.CacheName]enum.CacheGroupName, serverTypes map[enum.CacheName]enum.CacheType, mc to.TrafficMonitorConfigMap, events health.ThreadsafeEvents) (dsdata.Stats, dsdata.LastStats) {
+	err := error(nil)
+	lastStat, lastStatExists := lastStats.DeliveryServices[dsName]
+	if !lastStatExists {
+		lastStat = newLastDSStat()
+	}
+
+	for cacheName, cacheStats := range stat.Caches {
+		lastStat.Caches[cacheName], err = addLastStats(lastStat.Caches[cacheName], cacheStats, dsStatsTime)
+		if err != nil {
+			log.Warnf("%v adding kbps for cache %v: %v", dsName, cacheName, err)
+			continue
+		}
+		cacheStats.Kbps.Value = lastStat.Caches[cacheName].Bytes.PerSec / BytesPerKilobit
+		stat.Caches[cacheName] = cacheStats
+	}
+
+	lastStat = addLastDSStatTotals(lastStat, stat.CommonStats.CachesReporting, serverCachegroups, serverTypes)
+
+	for cacheGroup, cacheGroupStat := range lastStat.CacheGroups {
+		stat.CacheGroups[cacheGroup] = addLastStatsToStatCacheStats(stat.CacheGroups[cacheGroup], cacheGroupStat)
+	}
+	for cacheType, cacheTypeStat := range lastStat.Type {
+		stat.Types[cacheType] = addLastStatsToStatCacheStats(stat.Types[cacheType], cacheTypeStat)
+	}
+	stat.TotalStats = addLastStatsToStatCacheStats(stat.TotalStats, lastStat.Total)
+	lastStats.DeliveryServices[dsName] = lastStat
+	errStr := getDsErrString(dsName, stat.TotalStats, mc)
+	if errStr != "" {
+		stat.CommonStats.IsAvailable.Value = false
+		stat.CommonStats.IsHealthy.Value = false
+		stat.CommonStats.ErrorStr.Value = errStr
+		events.Add(health.Event{Time: time.Now(), Unix: time.Now().Unix(), Description: errStr, Name: dsName.String(), Hostname: dsName.String(), Type: "Delivery Service", Available: stat.CommonStats.IsAvailable.Value})
+	}
+
+	dsStats.DeliveryService[dsName] = stat
+	return dsStats, lastStats
+}
+
+// latestBytes returns the most recent OutBytes from the given cache results, and the time of that result. It assumes zero results are not valid, but nonzero results with errors are valid.
+func latestBytes(p cache.PrecomputedData) (int64, time.Time, error) {
+	if p.OutBytes == 0 {
+		return 0, time.Time{}, fmt.Errorf("no valid results")
+	}
+	return p.OutBytes, p.Time, nil
+}
+
+// addCachePerSecStats calculates the cache per-second stats, adds them to LastStats, and returns the augmented object.
+func addCachePerSecStats(cacheName enum.CacheName, precomputed cache.PrecomputedData, lastStats dsdata.LastStats) dsdata.LastStats {
+	outBytes, outBytesTime, err := latestBytes(precomputed) // it's ok if `latestBytes` returns 0s with an error, `addLastStat` will refrain from setting it (unless the previous calculation was nonzero, in which case it will error appropriately).
+	if err != nil {
+		log.Warnf("while computing delivery service data for cache %v: %v\n", cacheName, err)
+	}
+	lastStat := lastStats.Caches[cacheName] // if lastStats.Caches[cacheName] doesn't exist, it will be zero-constructed, and `addLastStat` will refrain from setting the PerSec for zero LastStats
+	lastStat.Bytes, err = addLastStat(lastStat.Bytes, outBytes, outBytesTime)
+	if err != nil {
+		log.Warnf("while computing delivery service data for cache %v: %v\n", cacheName, err)
+		return lastStats
+	}
+	lastStats.Caches[cacheName] = lastStat
+
+	return lastStats
+}
+
+// addPerSecStats adds Kbps fields to the NewStats, based on the previous out_bytes in the oldStats, and the time difference.
+//
+// Traffic Server only updates its data every N seconds. So, often we get a new Stats with the same OutBytes as the previous one,
+// So, we must record the last changed value, and the time it changed. Then, if the new OutBytes is different from the previous,
+// we set the (new - old) / lastChangedTime as the KBPS, and update the recorded LastChangedTime and LastChangedValue
+//
+// TODO handle ATS byte rolling (when the `out_bytes` overflows back to 0)
+func addPerSecStats(precomputed map[enum.CacheName]cache.PrecomputedData, dsStats dsdata.Stats, lastStats dsdata.LastStats, dsStatsTime time.Time, serverCachegroups map[enum.CacheName]enum.CacheGroupName, serverTypes map[enum.CacheName]enum.CacheType, mc to.TrafficMonitorConfigMap, events health.ThreadsafeEvents) (dsdata.Stats, dsdata.LastStats) {
+	for dsName, stat := range dsStats.DeliveryService {
+		dsStats, lastStats = addDSPerSecStats(dsName, stat, lastStats, dsStats, dsStatsTime, serverCachegroups, serverTypes, mc, events)
+	}
+	for cacheName, precomputedData := range precomputed {
+		lastStats = addCachePerSecStats(cacheName, precomputedData, lastStats)
+	}
+
+	return dsStats, lastStats
+}
+
+// CreateStats aggregates and creates statistics from given precomputed stat history. It returns the created stats, information about these stats necessary for the next calculation, and any error.
+func CreateStats(precomputed map[enum.CacheName]cache.PrecomputedData, toData todata.TOData, crStates peer.Crstates, lastStats dsdata.LastStats, now time.Time, mc to.TrafficMonitorConfigMap, events health.ThreadsafeEvents) (dsdata.Stats, dsdata.LastStats, error) {
+	start := time.Now()
+	dsStats := dsdata.NewStats()
+	for deliveryService := range toData.DeliveryServiceServers {
+		if deliveryService == "" {
+			log.Errorf("EMPTY CreateStats deliveryService")
+			continue
+		}
+		dsStats.DeliveryService[deliveryService] = *dsdata.NewStat()
+	}
+	dsStats = setStaticData(dsStats, toData.DeliveryServiceServers)
+	var err error
+	dsStats, err = addAvailableData(dsStats, crStates, toData.ServerCachegroups, toData.ServerDeliveryServices, toData.ServerTypes, precomputed) // TODO move after stat summarisation
+	if err != nil {
+		return dsStats, lastStats, fmt.Errorf("Error getting Cache availability data: %v", err)
+	}
+
+	for server, precomputedData := range precomputed {
+		cachegroup, ok := toData.ServerCachegroups[server]
+		if !ok {
+			log.Warnf("server %s has no cachegroup, skipping\n", server)
+			continue
+		}
+		serverType, ok := toData.ServerTypes[server]
+		if !ok {
+			log.Warnf("server %s not in CRConfig, skipping\n", server)
+			continue
+		}
+
+		// TODO check result.PrecomputedData.Errors
+		for ds, resultStat := range precomputedData.DeliveryServiceStats {
+			if ds == "" {
+				log.Errorf("EMPTY precomputed delivery service")
+				continue
+			}
+
+			if _, ok := dsStats.DeliveryService[ds]; !ok {
+				dsStats.DeliveryService[ds] = resultStat
+				continue
+			}
+			httpDsStat := dsStats.DeliveryService[ds]
+			httpDsStat.TotalStats = httpDsStat.TotalStats.Sum(resultStat.TotalStats)
+			httpDsStat.CacheGroups[cachegroup] = httpDsStat.CacheGroups[cachegroup].Sum(resultStat.CacheGroups[cachegroup])
+			httpDsStat.Types[serverType] = httpDsStat.Types[serverType].Sum(resultStat.Types[serverType])
+			httpDsStat.Caches[server] = httpDsStat.Caches[server].Sum(resultStat.Caches[server])
+			httpDsStat.CachesTimeReceived[server] = resultStat.CachesTimeReceived[server]
+			httpDsStat.CommonStats = dsStats.DeliveryService[ds].CommonStats
+			dsStats.DeliveryService[ds] = httpDsStat // TODO determine if necessary
+		}
+	}
+
+	perSecStats, lastStats := addPerSecStats(precomputed, dsStats, lastStats, now, toData.ServerCachegroups, toData.ServerTypes, mc, events)
+	log.Infof("CreateStats took %v\n", time.Since(start))
+	perSecStats.Time = time.Now()
+	return perSecStats, lastStats, nil
+}
+
+func getDsErrString(dsName enum.DeliveryServiceName, dsStats dsdata.StatCacheStats, monitorConfig to.TrafficMonitorConfigMap) string {
+	tpsThreshold := monitorConfig.DeliveryService[dsName.String()].TotalTPSThreshold
+	if tpsThreshold > 0 && dsStats.TpsTotal.Value > float64(tpsThreshold) {
+		return fmt.Sprintf("total.tps_total too high (%v > %v)", dsStats.TpsTotal.Value, tpsThreshold)
+	}
+
+	kbpsThreshold := monitorConfig.DeliveryService[dsName.String()].TotalKbpsThreshold
+	if kbpsThreshold > 0 && dsStats.Kbps.Value > float64(kbpsThreshold) {
+		return fmt.Sprintf("total.kbps too high (%v > %v)", dsStats.Kbps.Value, kbpsThreshold)
+	}
+	return ""
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/deliveryservicedata/stat.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/deliveryservicedata/stat.go b/traffic_monitor_golang/traffic_monitor/deliveryservicedata/stat.go
new file mode 100644
index 0000000..07d9f33
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/deliveryservicedata/stat.go
@@ -0,0 +1,488 @@
+package deliveryservicedata // TODO rename?
+
+/*
+ * 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 (
+	"errors"
+	"fmt"
+	"net/url"
+	"strconv"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/srvhttp"
+)
+
+// Filter encapsulates functions to filter a given set of Stats, e.g. from HTTP query parameters.
+// TODO combine with cache.Filter?
+type Filter interface {
+	UseStat(name string) bool
+	UseDeliveryService(name enum.DeliveryServiceName) bool
+	WithinStatHistoryMax(int) bool
+}
+
+// StatName is the name of a stat.
+type StatName string
+
+// StatOld is the old JSON representation of a stat, from Traffic Monitor 1.0.
+type StatOld struct {
+	Time  int64       `json:"time"`
+	Value interface{} `json:"value"`
+	Span  int         `json:"span,omitempty"`  // TODO set? remove?
+	Index int         `json:"index,omitempty"` // TODO set? remove?
+}
+
+// StatsOld is the old JSON representation of stats, from Traffic Monitor 1.0. It is designed to be serialized and returns from an API, and includes stat history for each delivery service, as well as data common to most endpoints.
+type StatsOld struct {
+	DeliveryService map[enum.DeliveryServiceName]map[StatName][]StatOld `json:"deliveryService"`
+	srvhttp.CommonAPIData
+}
+
+// StatsReadonly is a read-only interface for delivery service Stats, designed to be passed to multiple goroutine readers.
+type StatsReadonly interface {
+	Get(enum.DeliveryServiceName) (StatReadonly, bool)
+	JSON(Filter, url.Values) StatsOld
+}
+
+// StatReadonly is a read-only interface for a delivery service Stat, designed to be passed to multiple goroutine readers.
+type StatReadonly interface {
+	Copy() Stat
+	Common() StatCommonReadonly
+	CacheGroup(name enum.CacheGroupName) (StatCacheStats, bool)
+	Type(name enum.CacheType) (StatCacheStats, bool)
+	Total() StatCacheStats
+}
+
+// StatCommonReadonly is a read-only interface for a delivery service's common Stat data, designed to be passed to multiple goroutine readers.
+type StatCommonReadonly interface {
+	Copy() StatCommon
+	CachesConfigured() StatInt
+	CachesReportingNames() []enum.CacheName
+	Error() StatString
+	Status() StatString
+	Healthy() StatBool
+	Available() StatBool
+	CachesAvailable() StatInt
+}
+
+// StatMeta includes metadata about a particular stat.
+type StatMeta struct {
+	Time int64 `json:"time"`
+}
+
+// StatFloat is a float stat, combined with its metadata
+type StatFloat struct {
+	StatMeta
+	Value float64 `json:"value"`
+}
+
+// StatBool is a boolean stat, combined with its metadata
+type StatBool struct {
+	StatMeta
+	Value bool `json:"value"`
+}
+
+// StatInt is an integer stat, combined with its metadata
+type StatInt struct {
+	StatMeta
+	Value int64 `json:"value"`
+}
+
+// StatString is a string stat, combined with its metadata
+type StatString struct {
+	StatMeta
+	Value string `json:"value"`
+}
+
+// StatCommon contains stat data common to most delivery service stats.
+type StatCommon struct {
+	CachesConfiguredNum StatInt                 `json:"caches_configured"`
+	CachesReporting     map[enum.CacheName]bool `json:"caches_reporting"`
+	ErrorStr            StatString              `json:"error_string"`
+	StatusStr           StatString              `json:"status"`
+	IsHealthy           StatBool                `json:"is_healthy"`
+	IsAvailable         StatBool                `json:"is_available"`
+	CachesAvailableNum  StatInt                 `json:"caches_available"`
+	CachesDisabled      []string                `json:"disabled_locations"`
+}
+
+// Copy returns a deep copy of this StatCommon object.
+func (a StatCommon) Copy() StatCommon {
+	b := a
+	for k, v := range a.CachesReporting {
+		b.CachesReporting[k] = v
+	}
+	b.CachesDisabled = make([]string, len(a.CachesDisabled), len(a.CachesDisabled))
+	for i, v := range a.CachesDisabled {
+		b.CachesDisabled[i] = v
+	}
+	return b
+}
+
+// CachesConfigured returns the number of caches configured for this delivery service stat. It is part of the StatCommonReadonly interface.
+func (a StatCommon) CachesConfigured() StatInt {
+	return a.CachesConfiguredNum
+}
+
+// CacheReporting returns the number of caches reporting for this delivery service stat. It is part of the StatCommonReadonly interface.
+func (a StatCommon) CacheReporting(name enum.CacheName) (bool, bool) {
+	c, ok := a.CachesReporting[name]
+	return c, ok
+}
+
+// CachesReportingNames returns the list of caches reporting for this delivery service stat. It is part of the StatCommonReadonly interface.
+func (a StatCommon) CachesReportingNames() []enum.CacheName {
+	names := make([]enum.CacheName, 0, len(a.CachesReporting))
+	for name := range a.CachesReporting {
+		names = append(names, name)
+	}
+	return names
+}
+
+// Error returns the error string of this delivery service stat. It is part of the StatCommonReadonly interface.
+func (a StatCommon) Error() StatString {
+	return a.ErrorStr
+}
+
+// Status returns the status string of this delivery service stat. It is part of the StatCommonReadonly interface.
+func (a StatCommon) Status() StatString {
+	return a.StatusStr
+}
+
+// Healthy returns whether this delivery service is considered healthy by this stat. It is part of the StatCommonReadonly interface.
+func (a StatCommon) Healthy() StatBool {
+	return a.IsHealthy
+}
+
+// Available returns whether this delivery service is considered available by this stat. It is part of the StatCommonReadonly interface.
+func (a StatCommon) Available() StatBool {
+	return a.IsAvailable
+}
+
+// CachesAvailable returns the number of caches available to the delivery service in this stat. It is part of the StatCommonReadonly interface.
+func (a StatCommon) CachesAvailable() StatInt {
+	return a.CachesAvailableNum
+}
+
+// StatCacheStats is all the stats generated by a cache.
+// This may also be used for aggregate stats, for example, the summary of all cache stats for a cache group, or delivery service.
+// Each stat is an array, in case there are multiple data points at different times. However, a single data point i.e. a single array member is common.
+type StatCacheStats struct {
+	OutBytes    StatInt    `json:"out_bytes"`
+	IsAvailable StatBool   `json:"is_available"`
+	Status5xx   StatInt    `json:"status_5xx"`
+	Status4xx   StatInt    `json:"status_4xx"`
+	Status3xx   StatInt    `json:"status_3xx"`
+	Status2xx   StatInt    `json:"status_2xx"`
+	InBytes     StatFloat  `json:"in_bytes"`
+	Kbps        StatFloat  `json:"kbps"`
+	Tps5xx      StatFloat  `json:"tps_5xx"`
+	Tps4xx      StatFloat  `json:"tps_4xx"`
+	Tps3xx      StatFloat  `json:"tps_3xx"`
+	Tps2xx      StatFloat  `json:"tps_2xx"`
+	ErrorString StatString `json:"error_string"`
+	TpsTotal    StatFloat  `json:"tps_total"`
+}
+
+// Sum adds the given cache stats to this cache stats. Numeric values are summed; strings are appended.
+func (a StatCacheStats) Sum(b StatCacheStats) StatCacheStats {
+	return StatCacheStats{
+		OutBytes:    StatInt{Value: a.OutBytes.Value + b.OutBytes.Value},
+		IsAvailable: StatBool{Value: a.IsAvailable.Value || b.IsAvailable.Value},
+		Status5xx:   StatInt{Value: a.Status5xx.Value + b.Status5xx.Value},
+		Status4xx:   StatInt{Value: a.Status4xx.Value + b.Status4xx.Value},
+		Status3xx:   StatInt{Value: a.Status3xx.Value + b.Status3xx.Value},
+		Status2xx:   StatInt{Value: a.Status2xx.Value + b.Status2xx.Value},
+		InBytes:     StatFloat{Value: a.InBytes.Value + b.InBytes.Value},
+		Kbps:        StatFloat{Value: a.Kbps.Value + b.Kbps.Value},
+		Tps5xx:      StatFloat{Value: a.Tps5xx.Value + b.Tps5xx.Value},
+		Tps4xx:      StatFloat{Value: a.Tps4xx.Value + b.Tps4xx.Value},
+		Tps3xx:      StatFloat{Value: a.Tps3xx.Value + b.Tps3xx.Value},
+		Tps2xx:      StatFloat{Value: a.Tps2xx.Value + b.Tps2xx.Value},
+		ErrorString: StatString{Value: a.ErrorString.Value + b.ErrorString.Value},
+		TpsTotal:    StatFloat{Value: a.TpsTotal.Value + b.TpsTotal.Value},
+	}
+}
+
+// Stat represents a complete delivery service stat, for a given poll, or at the time requested.
+type Stat struct {
+	CommonStats        StatCommon
+	CacheGroups        map[enum.CacheGroupName]StatCacheStats
+	Types              map[enum.CacheType]StatCacheStats
+	Caches             map[enum.CacheName]StatCacheStats
+	CachesTimeReceived map[enum.CacheName]time.Time
+	TotalStats         StatCacheStats
+}
+
+// ErrNotProcessedStat indicates a stat received is not used by Traffic Monitor, nor returned by any API endpoint. Receiving this error indicates the stat has been discarded.
+var ErrNotProcessedStat = errors.New("This stat is not used.")
+
+// NewStat returns a new delivery service Stat, initializing pointer members.
+func NewStat() *Stat {
+	return &Stat{
+		CacheGroups:        map[enum.CacheGroupName]StatCacheStats{},
+		Types:              map[enum.CacheType]StatCacheStats{},
+		CommonStats:        StatCommon{CachesReporting: map[enum.CacheName]bool{}},
+		Caches:             map[enum.CacheName]StatCacheStats{},
+		CachesTimeReceived: map[enum.CacheName]time.Time{},
+	}
+}
+
+// Copy performs a deep copy of this Stat. It does not modify, and is thus safe for multiple goroutines.
+func (a Stat) Copy() Stat {
+	b := Stat{
+		CommonStats:        a.CommonStats.Copy(),
+		TotalStats:         a.TotalStats,
+		CacheGroups:        map[enum.CacheGroupName]StatCacheStats{},
+		Types:              map[enum.CacheType]StatCacheStats{},
+		Caches:             map[enum.CacheName]StatCacheStats{},
+		CachesTimeReceived: map[enum.CacheName]time.Time{},
+	}
+	for k, v := range a.CacheGroups {
+		b.CacheGroups[k] = v
+	}
+	for k, v := range a.Types {
+		b.Types[k] = v
+	}
+	for k, v := range a.Caches {
+		b.Caches[k] = v
+	}
+	for k, v := range a.CachesTimeReceived {
+		b.CachesTimeReceived[k] = v
+	}
+	return b
+}
+
+// Common returns the common stat data for this stat. It is part of the StatCommonReadonly interface.
+func (a Stat) Common() StatCommonReadonly {
+	return a.CommonStats
+}
+
+// CacheGroup returns the data for the given cachegroup in this stat. It is part of the StatCommonReadonly interface.
+func (a Stat) CacheGroup(name enum.CacheGroupName) (StatCacheStats, bool) {
+	c, ok := a.CacheGroups[name]
+	return c, ok
+}
+
+// Type returns the aggregated data for the given cache type in this stat. It is part of the StatCommonReadonly interface.
+func (a Stat) Type(name enum.CacheType) (StatCacheStats, bool) {
+	t, ok := a.Types[name]
+	return t, ok
+}
+
+// Total returns the aggregated total data in this stat. It is part of the StatCommonReadonly interface.
+func (a Stat) Total() StatCacheStats {
+	return a.TotalStats
+}
+
+// Stats is the JSON-serialisable representation of delivery service Stats. It maps delivery service names to individual stat objects.
+type Stats struct {
+	DeliveryService map[enum.DeliveryServiceName]Stat `json:"deliveryService"`
+	Time            time.Time                         `json:"-"`
+}
+
+// Copy performs a deep copy of this Stats object.
+func (s Stats) Copy() Stats {
+	b := NewStats()
+	for k, v := range s.DeliveryService {
+		b.DeliveryService[k] = v.Copy()
+	}
+	b.Time = s.Time
+	return b
+}
+
+// Get returns the stats for the given delivery service, and whether it exists.
+func (s Stats) Get(name enum.DeliveryServiceName) (StatReadonly, bool) {
+	ds, ok := s.DeliveryService[name]
+	return ds, ok
+}
+
+// JSON returns an object formatted as expected to be serialized to JSON and served.
+func (s Stats) JSON(filter Filter, params url.Values) StatsOld {
+	// TODO fix to be the time calculated, not the time requested
+	now := s.Time.UnixNano() / int64(time.Millisecond) // Traffic Monitor 1.0 API is 'ms since the epoch'
+	jsonObj := &StatsOld{
+		CommonAPIData:   srvhttp.GetCommonAPIData(params, time.Now()),
+		DeliveryService: map[enum.DeliveryServiceName]map[StatName][]StatOld{},
+	}
+
+	for deliveryService, stat := range s.DeliveryService {
+		if !filter.UseDeliveryService(deliveryService) {
+			continue
+		}
+		jsonObj.DeliveryService[deliveryService] = map[StatName][]StatOld{}
+		jsonObj = addCommonData(jsonObj, &stat.CommonStats, deliveryService, now, filter)
+		for cacheGroup, cacheGroupStats := range stat.CacheGroups {
+			jsonObj = addStatCacheStats(jsonObj, cacheGroupStats, deliveryService, "location."+string(cacheGroup)+".", now, filter)
+		}
+		for cacheType, typeStats := range stat.Types {
+			jsonObj = addStatCacheStats(jsonObj, typeStats, deliveryService, "type."+cacheType.String()+".", now, filter)
+		}
+		jsonObj = addStatCacheStats(jsonObj, stat.TotalStats, deliveryService, "total.", now, filter)
+	}
+	return *jsonObj
+}
+
+// NewStats creates a new Stats object, initializing any pointer members.
+// TODO rename to just 'New'?
+func NewStats() Stats {
+	return Stats{DeliveryService: map[enum.DeliveryServiceName]Stat{}}
+}
+
+// LastStats includes the previously recieved stats for DeliveryServices and Caches, the stat itself, when it was received, and the stat value per second.
+type LastStats struct {
+	DeliveryServices map[enum.DeliveryServiceName]LastDSStat
+	Caches           map[enum.CacheName]LastStatsData
+}
+
+// NewLastStats returns a new LastStats object, initializing internal pointer values.
+func NewLastStats() LastStats {
+	return LastStats{DeliveryServices: map[enum.DeliveryServiceName]LastDSStat{}, Caches: map[enum.CacheName]LastStatsData{}}
+}
+
+// Copy performs a deep copy of this LastStats object.
+func (a LastStats) Copy() LastStats {
+	b := NewLastStats()
+	for k, v := range a.DeliveryServices {
+		b.DeliveryServices[k] = v.Copy()
+	}
+	for k, v := range a.Caches {
+		b.Caches[k] = v
+	}
+	return b
+}
+
+// LastDSStat maps and aggregates the last stats received for the given delivery service to caches, cache groups, types, and total.
+// TODO figure a way to associate this type with StatHTTP, with which its members correspond.
+type LastDSStat struct {
+	Caches      map[enum.CacheName]LastStatsData
+	CacheGroups map[enum.CacheGroupName]LastStatsData
+	Type        map[enum.CacheType]LastStatsData
+	Total       LastStatsData
+}
+
+// Copy performs a deep copy of this LastDSStat object.
+func (a LastDSStat) Copy() LastDSStat {
+	b := LastDSStat{
+		CacheGroups: map[enum.CacheGroupName]LastStatsData{},
+		Type:        map[enum.CacheType]LastStatsData{},
+		Caches:      map[enum.CacheName]LastStatsData{},
+		Total:       a.Total,
+	}
+	for k, v := range a.CacheGroups {
+		b.CacheGroups[k] = v
+	}
+	for k, v := range a.Type {
+		b.Type[k] = v
+	}
+	for k, v := range a.Caches {
+		b.Caches[k] = v
+	}
+	return b
+}
+
+func newLastDSStat() LastDSStat {
+	return LastDSStat{
+		CacheGroups: map[enum.CacheGroupName]LastStatsData{},
+		Type:        map[enum.CacheType]LastStatsData{},
+		Caches:      map[enum.CacheName]LastStatsData{},
+	}
+}
+
+// LastStatsData contains the last stats and per-second calculations for bytes and status codes received from a cache.
+type LastStatsData struct {
+	Bytes     LastStatData
+	Status2xx LastStatData
+	Status3xx LastStatData
+	Status4xx LastStatData
+	Status5xx LastStatData
+}
+
+// Sum returns the Sum() of each member data with the given LastStatsData corresponding members
+func (a LastStatsData) Sum(b LastStatsData) LastStatsData {
+	return LastStatsData{
+		Bytes:     a.Bytes.Sum(b.Bytes),
+		Status2xx: a.Status2xx.Sum(b.Status2xx),
+		Status3xx: a.Status3xx.Sum(b.Status3xx),
+		Status4xx: a.Status4xx.Sum(b.Status4xx),
+		Status5xx: a.Status5xx.Sum(b.Status5xx),
+	}
+}
+
+// LastStatData contains the value, time it was received, and per-second calculation since the previous stat, for a stat from a cache.
+type LastStatData struct {
+	PerSec float64
+	Stat   int64
+	Time   time.Time
+}
+
+// Sum adds the PerSec and Stat of the given data to this object. Time is meaningless for the summed object, and is thus set to 0.
+func (a LastStatData) Sum(b LastStatData) LastStatData {
+	return LastStatData{
+		PerSec: a.PerSec + b.PerSec,
+		Stat:   a.Stat + b.Stat,
+	}
+}
+
+func addCommonData(s *StatsOld, c *StatCommon, deliveryService enum.DeliveryServiceName, t int64, filter Filter) *StatsOld {
+	add := func(name string, val interface{}) {
+		if filter.UseStat(name) {
+			s.DeliveryService[deliveryService][StatName(name)] = []StatOld{StatOld{Time: t, Value: val}}
+		}
+	}
+	add("caches-configured", fmt.Sprintf("%d", c.CachesConfiguredNum.Value))
+	add("caches-reporting", fmt.Sprintf("%d", len(c.CachesReporting)))
+	add("error-string", c.ErrorStr.Value)
+	add("status", c.StatusStr.Value)
+	add("isHealthy", fmt.Sprintf("%t", c.IsHealthy.Value))
+	add("isAvailable", fmt.Sprintf("%t", c.IsAvailable.Value))
+	add("caches-available", fmt.Sprintf("%d", c.CachesAvailableNum.Value))
+	add("disabledLocations", c.CachesDisabled)
+	return s
+}
+
+func addStatCacheStats(s *StatsOld, c StatCacheStats, deliveryService enum.DeliveryServiceName, prefix string, t int64, filter Filter) *StatsOld {
+	add := func(name, val string) {
+		if filter.UseStat(name) {
+			// This is for compatibility with the Traffic Monitor 1.0 API.
+			// TODO abstract this? Or deprecate and remove it?
+			if name == "isAvailable" || name == "error-string" {
+				s.DeliveryService[deliveryService][StatName("location."+prefix+name)] = []StatOld{StatOld{Time: t, Value: val}}
+			} else {
+				s.DeliveryService[deliveryService][StatName(prefix+name)] = []StatOld{StatOld{Time: t, Value: val}}
+			}
+		}
+	}
+	add("out_bytes", strconv.Itoa(int(c.OutBytes.Value)))
+	add("isAvailable", fmt.Sprintf("%t", c.IsAvailable.Value))
+	add("status_5xx", strconv.Itoa(int(c.Status5xx.Value)))
+	add("status_4xx", strconv.Itoa(int(c.Status4xx.Value)))
+	add("status_3xx", strconv.Itoa(int(c.Status3xx.Value)))
+	add("status_2xx", strconv.Itoa(int(c.Status2xx.Value)))
+	add("in_bytes", strconv.Itoa(int(c.InBytes.Value)))
+	add("kbps", strconv.Itoa(int(c.Kbps.Value)))
+	add("tps_5xx", fmt.Sprintf("%f", c.Tps5xx.Value))
+	add("tps_4xx", fmt.Sprintf("%f", c.Tps4xx.Value))
+	add("tps_3xx", fmt.Sprintf("%f", c.Tps3xx.Value))
+	add("tps_2xx", fmt.Sprintf("%f", c.Tps2xx.Value))
+	add("error-string", c.ErrorString.Value)
+	add("tps_total", fmt.Sprintf("%f", c.TpsTotal.Value))
+	return s
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/enum/enum.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/enum/enum.go b/traffic_monitor_golang/traffic_monitor/enum/enum.go
new file mode 100644
index 0000000..45546bd
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/enum/enum.go
@@ -0,0 +1,181 @@
+// Package enum contains enumerations and strongly typed names.
+//
+// These enums should be treated as enumerables, and MUST NOT be cast as anything else (integer, strings, etc). Enums MUST NOT be compared to strings or integers via casting. Enumerable data SHOULD be stored as the enumeration, not as a string or number. The *only* reason they are internally represented as strings, is to make them implicitly serialize to human-readable JSON. They should not be treated as strings. Casting or storing strings or numbers defeats the purpose of enum safety and conveniences.
+//
+// When storing enumumerable data in memory, it SHOULD be converted to and stored as an enum via the corresponding `FromString` function, checked whether the conversion failed and Invalid values handled, and valid data stored as the enum. This guarantees stored data is valid, and catches invalid input as soon as possible.
+//
+// When adding new enum types, enums should be internally stored as strings, so they implicitly serialize as human-readable JSON, unless the performance or memory of integers is necessary (it almost certainly isn't). Enums should always have the "invalid" value as the empty string (or 0), so default-initialized enums are invalid.
+// Enums should always have a FromString() conversion function, to convert input data to enums. Conversion functions should usually be case-insensitive, and may ignore underscores or hyphens, depending on the use case.
+//
+package enum
+
+/*
+ * 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 (
+	"strings"
+)
+
+// TrafficMonitorName is the hostname of a Traffic Monitor peer.
+type TrafficMonitorName string
+
+// CacheName is the hostname of a CDN cache.
+type CacheName string
+
+// CacheGroupName is the name of a CDN cachegroup.
+type CacheGroupName string
+
+// DeliveryServiceName is the name of a CDN delivery service.
+type DeliveryServiceName string
+
+// CacheType is the type (or tier) of a CDN cache.
+type CacheType string
+
+const (
+	// CacheTypeEdge represents an edge cache.
+	CacheTypeEdge = CacheType("EDGE")
+	// CacheTypeMid represents a mid cache.
+	CacheTypeMid = CacheType("MID")
+	// CacheTypeInvalid represents an cache type enumeration. Note this is the default construction for a CacheType.
+	CacheTypeInvalid = CacheType("")
+)
+
+func (c CacheName) String() string {
+	return string(c)
+}
+
+func (t TrafficMonitorName) String() string {
+	return string(t)
+}
+
+func (d DeliveryServiceName) String() string {
+	return string(d)
+}
+
+// String returns a string representation of this cache type.
+func (t CacheType) String() string {
+	switch t {
+	case CacheTypeEdge:
+		return "EDGE"
+	case CacheTypeMid:
+		return "MID"
+	default:
+		return "INVALIDCACHETYPE"
+	}
+}
+
+// CacheTypeFromString returns a cache type object from its string representation, or CacheTypeInvalid if the string is not a valid type.
+func CacheTypeFromString(s string) CacheType {
+	s = strings.ToLower(s)
+	if strings.HasPrefix(s, "edge") {
+		return CacheTypeEdge
+	}
+	if strings.HasPrefix(s, "mid") {
+		return CacheTypeMid
+	}
+	return CacheTypeInvalid
+}
+
+// DSType is the Delivery Service type. HTTP, DNS, etc.
+type DSType string
+
+const (
+	// DSTypeHTTP represents an HTTP delivery service
+	DSTypeHTTP = DSType("http")
+	// DSTypeDNS represents a DNS delivery service
+	DSTypeDNS = DSType("dns")
+	// DSTypeInvalid represents an invalid delivery service type enumeration. Note this is the default construction for a DSType.
+	DSTypeInvalid = DSType("")
+)
+
+// String returns a string representation of this delivery service type.
+func (t DSType) String() string {
+	switch t {
+	case DSTypeHTTP:
+		return "HTTP"
+	case DSTypeDNS:
+		return "DNS"
+	default:
+		return "INVALIDDSTYPE"
+	}
+}
+
+// DSTypeFromString returns a delivery service type object from its string representation, or DSTypeInvalid if the string is not a valid type.
+func DSTypeFromString(s string) DSType {
+	s = strings.ToLower(s)
+	switch s {
+	case "http":
+		return DSTypeHTTP
+	case "dns":
+		return DSTypeDNS
+	default:
+		return DSTypeInvalid
+	}
+}
+
+// CacheStatus represents the Traffic Server status set in Traffic Ops (online, offline, admin_down, reported). The string values of this type should match the Traffic Ops values.
+type CacheStatus string
+
+const (
+	// CacheStatusAdminDown represents a cache which has been administratively marked as down, but which should still appear in the CDN (Traffic Server, Traffic Monitor, Traffic Router).
+	CacheStatusAdminDown = CacheStatus("ADMIN_DOWN")
+	// CacheStatusOnline represents a cache which has been marked as Online in Traffic Ops, irrespective of monitoring. Traffic Monitor will always flag these caches as available.
+	CacheStatusOnline = CacheStatus("ONLINE")
+	// CacheStatusOffline represents a cache which has been marked as Offline in Traffic Ops. These caches will not be returned in any endpoint, and Traffic Monitor acts like they don't exist.
+	CacheStatusOffline = CacheStatus("OFFLINE")
+	// CacheStatusReported represents a cache which has been marked as Reported in Traffic Ops. These caches are polled for health and returned in endpoints as available or unavailable based on bandwidth, response time, and other factors. The vast majority of caches should be Reported.
+	CacheStatusReported = CacheStatus("REPORTED")
+	// CacheStatusInvalid represents an invalid status enumeration.
+	CacheStatusInvalid = CacheStatus("")
+)
+
+// String returns a string representation of this cache status
+func (t CacheStatus) String() string {
+	switch t {
+	case CacheStatusAdminDown:
+		fallthrough
+	case CacheStatusOnline:
+		fallthrough
+	case CacheStatusOffline:
+		fallthrough
+	case CacheStatusReported:
+		return string(t)
+	default:
+		return "INVALIDCACHESTATUS"
+	}
+}
+
+// CacheStatusFromString returns a CacheStatus from its string representation, or CacheStatusInvalid if the string is not a valid type.
+func CacheStatusFromString(s string) CacheStatus {
+	s = strings.ToLower(s)
+	switch s {
+	case "admin_down":
+		fallthrough
+	case "admindown":
+		return CacheStatusAdminDown
+	case "offline":
+		return CacheStatusOffline
+	case "online":
+		return CacheStatusOnline
+	case "reported":
+		return CacheStatusReported
+	default:
+		return CacheStatusInvalid
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/health/cache.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/health/cache.go b/traffic_monitor_golang/traffic_monitor/health/cache.go
new file mode 100644
index 0000000..7cf6eb5
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/health/cache.go
@@ -0,0 +1,272 @@
+package health
+
+/*
+ * 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"
+	"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"
+	"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"
+	to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
+)
+
+// GetVitals Gets the vitals to decide health on in the right format
+func GetVitals(newResult *cache.Result, prevResult *cache.Result, mc *to.TrafficMonitorConfigMap) {
+	if newResult.Error != nil {
+		log.Errorf("cache_health.GetVitals() called with an errored Result!")
+		return
+	}
+	// proc.loadavg -- we're using the 1 minute average (!?)
+	// value looks like: "0.20 0.07 0.07 1/967 29536" (without the quotes)
+	loadAverages := strings.Fields(newResult.Astats.System.ProcLoadavg)
+	if len(loadAverages) > 0 {
+		oneMinAvg, err := strconv.ParseFloat(loadAverages[0], 64)
+		if err != nil {
+			setErr(newResult, fmt.Errorf("Error converting load average string '%s': %v", newResult.Astats.System.ProcLoadavg, err))
+			return
+		}
+		newResult.Vitals.LoadAvg = oneMinAvg
+	} else {
+		setErr(newResult, fmt.Errorf("Can't make sense of '%s' as a load average for %s", newResult.Astats.System.ProcLoadavg, newResult.ID))
+		return
+	}
+
+	// proc.net.dev -- need to compare to prevSample
+	// value looks like
+	// "bond0:8495786321839 31960528603    0    0    0     0          0   2349716 143283576747316 101104535041    0    0    0     0       0          0"
+	// (without the quotes)
+	parts := strings.Split(newResult.Astats.System.ProcNetDev, ":")
+	if len(parts) > 1 {
+		numbers := strings.Fields(parts[1])
+		var err error
+		newResult.Vitals.BytesOut, err = strconv.ParseInt(numbers[8], 10, 64)
+		if err != nil {
+			setErr(newResult, fmt.Errorf("Error converting BytesOut from procnetdev: %v", err))
+			return
+		}
+		newResult.Vitals.BytesIn, err = strconv.ParseInt(numbers[0], 10, 64)
+		if err != nil {
+			setErr(newResult, fmt.Errorf("Error converting BytesIn from procnetdev: %v", err))
+			return
+		}
+		if prevResult != nil && prevResult.Vitals.BytesOut != 0 {
+			elapsedTimeInSecs := float64(newResult.Time.UnixNano()-prevResult.Time.UnixNano()) / 1000000000
+			newResult.Vitals.KbpsOut = int64(float64(((newResult.Vitals.BytesOut - prevResult.Vitals.BytesOut) * 8 / 1000)) / elapsedTimeInSecs)
+		} else {
+			// log.Infoln("prevResult == nil for id " + newResult.Id + ". Hope we're just starting up?")
+		}
+	} else {
+		setErr(newResult, fmt.Errorf("Error parsing procnetdev: no fields found"))
+		return
+	}
+
+	// inf.speed -- value looks like "10000" (without the quotes) so it is in Mbps.
+	// TODO JvD: Should we really be running this code every second for every cache polled????? I don't think so.
+	interfaceBandwidth := newResult.Astats.System.InfSpeed
+	newResult.Vitals.MaxKbpsOut = int64(interfaceBandwidth) * 1000
+
+	// log.Infoln(newResult.Id, "BytesOut", newResult.Vitals.BytesOut, "BytesIn", newResult.Vitals.BytesIn, "Kbps", newResult.Vitals.KbpsOut, "max", newResult.Vitals.MaxKbpsOut)
+}
+
+// EvalCache returns whether the given cache should be marked available, a string describing why, and which stat exceeded a threshold. The `stats` may be nil, for pollers which don't poll stats.
+// The availability of EvalCache MAY NOT be used to directly set the cache's local availability, because the threshold stats may not be part of the poller which produced the result. Rather, if the cache was previously unavailable from a threshold, it must be verified that threshold stat is in the results before setting the cache to available.
+// TODO change to return a `cache.AvailableStatus`
+func EvalCache(result cache.ResultInfo, resultStats cache.ResultStatValHistory, mc *to.TrafficMonitorConfigMap) (bool, string, string) {
+	serverInfo, ok := mc.TrafficServer[string(result.ID)]
+	if !ok {
+		log.Errorf("Cache %v missing from from Traffic Ops Monitor Config - treating as OFFLINE\n", result.ID)
+		return false, "ERROR - server missing in Traffic Ops monitor config", ""
+	}
+	serverProfile, ok := mc.Profile[serverInfo.Profile]
+	if !ok {
+		log.Errorf("Cache %v profile %v missing from from Traffic Ops Monitor Config - treating as OFFLINE\n", result.ID, serverInfo.Profile)
+		return false, "ERROR - server profile missing in Traffic Ops monitor config", ""
+	}
+
+	status := enum.CacheStatusFromString(serverInfo.Status)
+	if status == enum.CacheStatusInvalid {
+		log.Errorf("Cache %v got invalid status from Traffic Ops '%v' - treating as Reported\n", result.ID, serverInfo.Status)
+	}
+
+	availability := "available"
+	if !result.Available {
+		availability = "unavailable"
+	}
+
+	switch {
+	case status == enum.CacheStatusInvalid:
+		log.Errorf("Cache %v got invalid status from Traffic Ops '%v' - treating as OFFLINE\n", result.ID, serverInfo.Status)
+		return false, eventDesc(status, availability+"; invalid status"), ""
+	case status == enum.CacheStatusAdminDown:
+		return false, eventDesc(status, availability), ""
+	case status == enum.CacheStatusOffline:
+		log.Errorf("Cache %v set to offline, but still polled\n", result.ID)
+		return false, eventDesc(status, availability), ""
+	case status == enum.CacheStatusOnline:
+		return true, eventDesc(status, availability), ""
+	case result.Error != nil:
+		return false, eventDesc(status, fmt.Sprintf("%v", result.Error)), ""
+	}
+
+	computedStats := cache.ComputedStats()
+
+	for stat, threshold := range serverProfile.Parameters.Thresholds {
+		resultStat := interface{}(nil)
+		if computedStatF, ok := computedStats[stat]; ok {
+			dummyCombinedstate := peer.IsAvailable{} // the only stats which use combinedState are things like isAvailable, which don't make sense to ever be thresholds.
+			resultStat = computedStatF(result, serverInfo, serverProfile, dummyCombinedstate)
+		} else {
+			if resultStats == nil {
+				continue
+			}
+			resultStatHistory, ok := resultStats[stat]
+			if !ok {
+				continue
+			}
+			if len(resultStatHistory) < 1 {
+				continue
+			}
+			resultStat = resultStatHistory[0].Val
+		}
+
+		resultStatNum, ok := util.ToNumeric(resultStat)
+		if !ok {
+			log.Errorf("health.EvalCache threshold stat %s was not a number: %v", stat, resultStat)
+			continue
+		}
+
+		if !inThreshold(threshold, resultStatNum) {
+			return false, eventDesc(status, exceedsThresholdMsg(stat, threshold, resultStatNum)), stat
+		}
+	}
+
+	return result.Available, eventDesc(status, availability), ""
+}
+
+// CalcAvailability calculates the availability of the cache, from the given result. Availability is stored in `localCacheStatus` and `localStates`, and if the status changed an event is added to `events`. statResultHistory may be nil, for pollers which don't poll stats.
+// TODO add enum for poller names?
+func CalcAvailability(results []cache.Result, pollerName string, statResultHistory cache.ResultStatHistory, mc to.TrafficMonitorConfigMap, toData todata.TOData, localCacheStatusThreadsafe threadsafe.CacheAvailableStatus, localStates peer.CRStatesThreadsafe, events ThreadsafeEvents) {
+	localCacheStatuses := localCacheStatusThreadsafe.Get().Copy()
+	for _, result := range results {
+		statResults := cache.ResultStatValHistory(nil)
+		if statResultHistory != nil {
+			statResults = statResultHistory[result.ID]
+		}
+
+		isAvailable, whyAvailable, unavailableStat := EvalCache(cache.ToInfo(result), statResults, &mc)
+
+		// if the cache is now Available, and was previously unavailable due to a threshold, make sure this poller contains the stat which exceeded the threshold.
+		if previousStatus, hasPreviousStatus := localCacheStatuses[result.ID]; isAvailable && hasPreviousStatus && !previousStatus.Available && previousStatus.UnavailableStat != "" {
+			if !result.HasStat(previousStatus.UnavailableStat) {
+				return
+			}
+		}
+		localCacheStatuses[result.ID] = cache.AvailableStatus{
+			Available:       isAvailable,
+			Status:          mc.TrafficServer[string(result.ID)].Status,
+			Why:             whyAvailable,
+			UnavailableStat: unavailableStat,
+			Poller:          pollerName,
+		} // TODO move within localStates?
+
+		if available, ok := localStates.GetCache(result.ID); !ok || available.IsAvailable != isAvailable {
+			log.Infof("Changing state for %s was: %t now: %t because %s poller: %v error: %v", result.ID, available.IsAvailable, isAvailable, whyAvailable, pollerName, result.Error)
+			events.Add(Event{Time: time.Now(), Description: whyAvailable + " (" + pollerName + ")", Name: string(result.ID), Hostname: string(result.ID), Type: toData.ServerTypes[result.ID].String(), Available: isAvailable})
+		}
+
+		localStates.SetCache(result.ID, peer.IsAvailable{IsAvailable: isAvailable})
+	}
+	calculateDeliveryServiceState(toData.DeliveryServiceServers, localStates)
+	localCacheStatusThreadsafe.Set(localCacheStatuses)
+}
+
+func setErr(newResult *cache.Result, err error) {
+	newResult.Error = err
+	newResult.Available = false
+}
+
+// ExceedsThresholdMsg returns a human-readable message for why the given value exceeds the threshold. It does NOT check whether the value actually exceeds the threshold; call `InThreshold` to check first.
+func exceedsThresholdMsg(stat string, threshold to.HealthThreshold, val float64) string {
+	switch threshold.Comparator {
+	case "=":
+		return fmt.Sprintf("%s not equal (%.2f != %.2f)", stat, val, threshold.Val)
+	case ">":
+		return fmt.Sprintf("%s too low (%.2f < %.2f)", stat, val, threshold.Val)
+	case "<":
+		return fmt.Sprintf("%s too high (%.2f > %.2f)", stat, val, threshold.Val)
+	case ">=":
+		return fmt.Sprintf("%s too low (%.2f <= %.2f)", stat, val, threshold.Val)
+	case "<=":
+		return fmt.Sprintf("%s too high (%.2f >= %.2f)", stat, val, threshold.Val)
+	default:
+		return fmt.Sprintf("ERROR: Invalid Threshold: %+v", threshold)
+	}
+}
+
+func inThreshold(threshold to.HealthThreshold, val float64) bool {
+	switch threshold.Comparator {
+	case "=":
+		return val == threshold.Val
+	case ">":
+		return val > threshold.Val
+	case "<":
+		return val < threshold.Val
+	case ">=":
+		return val >= threshold.Val
+	case "<=":
+		return val <= threshold.Val
+	default:
+		log.Errorf("Invalid Threshold: %+v", threshold)
+		return true // for safety, if a threshold somehow gets corrupted, don't start marking caches down.
+	}
+}
+
+func eventDesc(status enum.CacheStatus, message string) string {
+	return fmt.Sprintf("%s - %s", status, message)
+}
+
+// calculateDeliveryServiceState calculates the state of delivery services from the new cache state data `cacheState` and the CRConfig data `deliveryServiceServers` and puts the calculated state in the outparam `deliveryServiceStates`
+func calculateDeliveryServiceState(deliveryServiceServers map[enum.DeliveryServiceName][]enum.CacheName, states peer.CRStatesThreadsafe) {
+	deliveryServices := states.GetDeliveryServices()
+	for deliveryServiceName, deliveryServiceState := range deliveryServices {
+		if _, ok := deliveryServiceServers[deliveryServiceName]; !ok {
+			// log.Errorf("CRConfig does not have delivery service %s, but traffic monitor poller does; skipping\n", deliveryServiceName)
+			continue
+		}
+		deliveryServiceState.IsAvailable = false
+		deliveryServiceState.DisabledLocations = []enum.CacheName{} // it's important this isn't nil, so it serialises to the JSON `[]` instead of `null`
+		for _, server := range deliveryServiceServers[deliveryServiceName] {
+			if available, _ := states.GetCache(server); available.IsAvailable {
+				deliveryServiceState.IsAvailable = true
+			} else {
+				deliveryServiceState.DisabledLocations = append(deliveryServiceState.DisabledLocations, server)
+			}
+		}
+		states.SetDeliveryService(deliveryServiceName, deliveryServiceState)
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/594b8517/traffic_monitor_golang/traffic_monitor/health/event.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/health/event.go b/traffic_monitor_golang/traffic_monitor/health/event.go
new file mode 100644
index 0000000..4dd40a3
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/health/event.go
@@ -0,0 +1,83 @@
+package health
+
+/*
+ * 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 (
+	"sync"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log"
+)
+
+// Event represents an event change in aggregated data. For example, a cache being marked as unavailable.
+type Event struct {
+	Time        time.Time `json:"-"`
+	Index       uint64    `json:"index"`
+	Unix        int64     `json:"time"`
+	Description string    `json:"description"`
+	Name        string    `json:"name"`
+	Hostname    string    `json:"hostname"`
+	Type        string    `json:"type"`
+	Available   bool      `json:"isAvailable"`
+}
+
+// Events provides safe access for multiple goroutines readers and a single writer to a stored Events slice.
+type ThreadsafeEvents struct {
+	events    *[]Event
+	m         *sync.RWMutex
+	nextIndex *uint64
+	max       uint64
+}
+
+func copyEvents(a []Event) []Event {
+	b := make([]Event, len(a), len(a))
+	copy(b, a)
+	return b
+}
+
+// NewEvents creates a new single-writer-multiple-reader Threadsafe object
+func NewThreadsafeEvents(maxEvents uint64) ThreadsafeEvents {
+	i := uint64(0)
+	return ThreadsafeEvents{m: &sync.RWMutex{}, events: &[]Event{}, nextIndex: &i, max: maxEvents}
+}
+
+// Get returns the internal slice of Events for reading. This MUST NOT be modified. If modification is necessary, copy the slice.
+func (o *ThreadsafeEvents) Get() []Event {
+	o.m.RLock()
+	defer o.m.RUnlock()
+	return *o.events
+}
+
+// Add adds the given event. This is threadsafe for one writer, multiple readers. This MUST NOT be called by multiple threads, as it non-atomically fetches and adds.
+func (o *ThreadsafeEvents) Add(e Event) {
+	// host="hostname", type=EDGE, available=true, msg="REPORTED - available"
+	log.Eventf(e.Time, "host=\"%s\", type=%s, available=%t, msg=\"%s\"", e.Hostname, e.Type, e.Available, e.Description)
+	o.m.Lock() // TODO test removing
+	events := copyEvents(*o.events)
+	e.Index = *o.nextIndex
+	events = append([]Event{e}, events...)
+	if len(events) > int(o.max) {
+		events = (events)[:o.max-1]
+	}
+	// o.m.Lock()
+	*o.events = events
+	*o.nextIndex++
+	o.m.Unlock()
+}