Commit 58edd562 authored by Philipp Winter's avatar Philipp Winter
Browse files

Merge branch 'issue/4' into 'master'

Export Prometheus-based metrics

Closes #4

See merge request tpo/anti-censorship/bridgestrap!3
parents dc8dc959 05811608
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -47,6 +47,24 @@ func bridgeLineToAddrPort(bridgeLine string) (string, error) {
	}
}

// FracFunctional returns the fraction of bridges currently in the cache that
// are functional.
func (tc *TestCache) FracFunctional() float64 {

	if len((*tc).Entries) == 0 {
		return 0
	}

	numFunctional := 0
	for _, entry := range (*tc).Entries {
		if entry.Error == "" {
			numFunctional++
		}
	}

	return float64(numFunctional) / float64(len((*tc).Entries))
}

// WriteToDisk writes our test result cache to disk, allowing it to persist
// across program restarts.
func (tc *TestCache) WriteToDisk(cacheFile string) error {
@@ -134,4 +152,6 @@ func (tc *TestCache) AddEntry(bridgeLine string, result error, lastTested time.T
	tc.l.Lock()
	(*tc).Entries[addrPort] = &CacheEntry{errorStr, lastTested}
	tc.l.Unlock()

	metrics.FracFunctional.Set((*tc).FracFunctional())
}
+19 −0
Original line number Diff line number Diff line
@@ -11,6 +11,10 @@ import (
	"time"
)

func init() {
	InitMetrics()
}

func NewCache() *TestCache {
	return &TestCache{
		Entries:      make(map[string]*CacheEntry),
@@ -55,6 +59,21 @@ func TestCacheFunctions(t *testing.T) {
	}
}

func TestCacheFracFunctional(t *testing.T) {

	cache := NewCache()

	cache.AddEntry("1.1.1.1:1", nil, time.Now().UTC())
	cache.AddEntry("2.2.2.2:2", nil, time.Now().UTC())
	cache.AddEntry("3.3.3.3:3", nil, time.Now().UTC())
	cache.AddEntry("4.4.4.4:4", errors.New("error"), time.Now().UTC())

	expected := 0.75
	if cache.FracFunctional() != expected {
		t.Errorf("Expected fraction %.2f but got %.2f.", expected, cache.FracFunctional())
	}
}

func TestCacheExpiration(t *testing.T) {

	cache := NewCache()
+4 −4
Original line number Diff line number Diff line
@@ -3,9 +3,9 @@ module gitlab.torproject.org/tpo/anti-censorship/bridgestrap
go 1.13

require (
	git.torproject.org/pluggable-transports/snowflake.git v0.0.0-20191213231743-37aaaffa1521
	github.com/gorilla/mux v1.7.3
	git.torproject.org/pluggable-transports/snowflake.git v0.0.0-20201120061516-ece43cbfcfc3
	github.com/gorilla/mux v1.8.0
	github.com/prometheus/client_golang v1.8.0
	github.com/yawning/bulb v0.0.0-20170405033506-85d80d893c3d
	golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa // indirect
	golang.org/x/time v0.0.0-20191024005414-555d28b269f0
	golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
)
+441 −5

File changed.

Preview size limit exceeded, changes collapsed.

+22 −5
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ type TestResult struct {
// TestRequest represents a client's request to test a batch of bridges.
type TestRequest struct {
	BridgeLines []string `json:"bridge_lines"`
	resultChan  chan *TestResult
}

// limiter implements a rate limiter.  We allow 1 request per second on average
@@ -90,21 +91,23 @@ func Index(w http.ResponseWriter, r *http.Request) {
	SendHtmlResponse(w, IndexPage)
}

func testBridgeLines(bridgeLines []string) *TestResult {
func testBridgeLines(req *TestRequest) *TestResult {

	// Add cached bridge lines to the result.
	result := NewTestResult()
	remainingBridgeLines := []string{}
	numCached := 0
	for _, bridgeLine := range bridgeLines {
	for _, bridgeLine := range req.BridgeLines {
		if entry := cache.IsCached(bridgeLine); entry != nil {
			numCached++
			metrics.CacheHits.Inc()
			result.Bridges[bridgeLine] = &BridgeTest{
				Functional: entry.Error == "",
				LastTested: entry.Time,
				Error:      entry.Error,
			}
		} else {
			metrics.CacheMisses.Inc()
			remainingBridgeLines = append(remainingBridgeLines, bridgeLine)
		}
	}
@@ -115,12 +118,20 @@ func testBridgeLines(bridgeLines []string) *TestResult {
			numCached, len(remainingBridgeLines))

		start := time.Now()
		partialResult := torCtx.TestBridgeLines(remainingBridgeLines)
		req.resultChan = make(chan *TestResult)
		torCtx.RequestQueue <- req
		partialResult := <-req.resultChan
		result.Time = float64(time.Now().Sub(start).Seconds())
		result.Error = partialResult.Error

		// Cache partial test results and add them to our existing result object.
		for bridgeLine, bridgeTest := range partialResult.Bridges {
			cache.AddEntry(bridgeLine, errors.New(bridgeTest.Error), bridgeTest.LastTested)
			if bridgeTest.Functional {
				metrics.NumFunctionalBridges.Inc()
			} else {
				metrics.NumDysfunctionalBridges.Inc()
			}
			result.Bridges[bridgeLine] = bridgeTest
		}
	} else {
@@ -143,11 +154,14 @@ func testBridgeLines(bridgeLines []string) *TestResult {
		numDysfunctional,
		float64(numDysfunctional)/float64(len(result.Bridges))*100)

	metrics.CacheSize.Set(float64(len(cache.Entries)))

	return result
}

func BridgeState(w http.ResponseWriter, r *http.Request) {

	metrics.ApiNumRequests.Inc()
	b, err := ioutil.ReadAll(r.Body)
	defer r.Body.Close()
	if err != nil {
@@ -169,6 +183,7 @@ func BridgeState(w http.ResponseWriter, r *http.Request) {
		return
	}

	metrics.ApiNumValidRequests.Inc()
	if len(req.BridgeLines) > MaxBridgesPerReq {
		log.Printf("Got %d bridges in request but we only allow <= %d.", len(req.BridgeLines), MaxBridgesPerReq)
		http.Error(w, fmt.Sprintf("maximum of %d bridge lines allowed", MaxBridgesPerReq), http.StatusBadRequest)
@@ -176,7 +191,7 @@ func BridgeState(w http.ResponseWriter, r *http.Request) {
	}

	log.Printf("Got %d bridge lines from %s.", len(req.BridgeLines), r.RemoteAddr)
	result := testBridgeLines(req.BridgeLines)
	result := testBridgeLines(req)

	jsonResult, err := json.Marshal(result)
	if err != nil {
@@ -189,6 +204,7 @@ func BridgeState(w http.ResponseWriter, r *http.Request) {

func BridgeStateWeb(w http.ResponseWriter, r *http.Request) {

	metrics.WebNumRequests.Inc()
	r.ParseForm()
	// Rate-limit Web requests to prevent someone from abusing this service
	// as a port scanner.
@@ -202,7 +218,8 @@ func BridgeStateWeb(w http.ResponseWriter, r *http.Request) {
		return
	}

	result := testBridgeLines([]string{bridgeLine})
	metrics.WebNumValidRequests.Inc()
	result := testBridgeLines(&TestRequest{BridgeLines: []string{bridgeLine}})
	bridgeResult, exists := result.Bridges[bridgeLine]
	if !exists {
		log.Printf("Bug: Test result not part of our result map.")
Loading