Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Jordan Orelli | 7363c4b2c4 | 4 years ago |
@ -1,47 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
type gcounter struct {
|
|
||||||
id int
|
|
||||||
counts map[int]int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c gcounter) incr() { c.counts[c.id]++ }
|
|
||||||
|
|
||||||
func (c gcounter) total() int {
|
|
||||||
var n int
|
|
||||||
for _, count := range c.counts {
|
|
||||||
n += count
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c gcounter) merge(other gcounter) {
|
|
||||||
for id, count := range other.counts {
|
|
||||||
if c.[id] < count {
|
|
||||||
c.[id] = count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type pair [2]int
|
|
||||||
|
|
||||||
func (c gcounter) MarshalJSON() ([]byte, error) {
|
|
||||||
pairs := make([]pair, 0, len(c.counts))
|
|
||||||
for id, count := range c.counts {
|
|
||||||
pairs = append(pairs, pair{id, count})
|
|
||||||
}
|
|
||||||
sort.Slice(pairs, func(i, j int) bool { return pairs[i][0] < pairs[j][0] })
|
|
||||||
return json.Marshal(pairs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *gcounter) UnmarshalJSON(b []byte) error {
|
|
||||||
pairs := make([]pair, 0, len(c.counts))
|
|
||||||
if err := json.Unmarshal(b, &pairs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGCounterJSON(t *testing.T) {
|
|
||||||
t.Run("marshal", func(t *testing.T) {
|
|
||||||
type test struct {
|
|
||||||
counter gcounter
|
|
||||||
expect string
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []test{
|
|
||||||
{
|
|
||||||
gcounter{1, map[int]int{1: 5, 3: 8}},
|
|
||||||
`[[1,5],[3,8]]`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
gcounter{8, map[int]int{3: 10, 7: 16, 12: 9, 2: 37}},
|
|
||||||
`[[2,37],[3,10],[7,16],[12,9]]`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
b, err := json.Marshal(test.counter)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("marshal failed: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s := string(b)
|
|
||||||
if s != test.expect {
|
|
||||||
t.Errorf("expected json: %s received: %s", test.expect, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("unmarshal", func(t *testing.T) {
|
|
||||||
type test struct {
|
|
||||||
in string
|
|
||||||
expect gcounter
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []test{
|
|
||||||
{
|
|
||||||
`[[1,5],[3,8]]`,
|
|
||||||
gcounter{1, map[int]int{1: 5, 3: 8}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[[2,37],[3,10],[7,16],[12,9]]`,
|
|
||||||
gcounter{8, map[int]int{3: 10, 7: 16, 12: 9, 2: 37}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
var c gcounter
|
|
||||||
if err := json.Unmarshal(test.in, &c); err != nil {
|
|
||||||
t.Errorf("unmarshal failed: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for id, count := range c.counts {
|
|
||||||
n := test.expect.counts[id]
|
|
||||||
if n != count {
|
|
||||||
t.Errorf("mismatched counts for id %d: expected %d, saw %d", id, count, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
package main
|
|
@ -1,78 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type response struct {
|
|
||||||
OK bool `json:"ok"`
|
|
||||||
Hits int `json:"hits"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// server implements a clustered http hit-counter server. Each path is given a
|
|
||||||
// g-counter, and every node in the cluster acts as a read-write replica.
|
|
||||||
type server struct {
|
|
||||||
sync.Mutex
|
|
||||||
|
|
||||||
// my own id
|
|
||||||
id int
|
|
||||||
|
|
||||||
// a mapping of peer servers id -> addr
|
|
||||||
peers map[int]string
|
|
||||||
|
|
||||||
// distributed counts
|
|
||||||
counters map[string]gcounter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch r.URL.Path {
|
|
||||||
case "/join":
|
|
||||||
s.join(w, r)
|
|
||||||
case "/sync":
|
|
||||||
s.sync(w, r)
|
|
||||||
default:
|
|
||||||
s.countHit(w, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) countHit(w http.ResponseWriter, r *http.Request) {
|
|
||||||
hits := s.hit(r.URL.Path)
|
|
||||||
|
|
||||||
fmt.Printf("% 8d %s\n", hits, r.URL.Path)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
json.NewEncoder(w).Encode(response{OK: true, Hits: hits})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) join(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var body struct {
|
|
||||||
Addr string `json:"addr"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) sync(w http.ResponseWriter, r *http.Request) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) hit(path string) int {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
|
|
||||||
if s.counters == nil {
|
|
||||||
s.counters = map[string]gcounter{
|
|
||||||
gcounter{s.id, map[int]int{id: 1}},
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
c, ok := s.counters[path]
|
|
||||||
if !ok {
|
|
||||||
s.counters[path] = gcounter{s.id, map[int]int{id: 1}}
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
c.incr()
|
|
||||||
return c.total()
|
|
||||||
}
|
|
Loading…
Reference in New Issue