Compare commits

..

2 Commits

Author SHA1 Message Date
efim
ea85460c0a fix: remove all marks when noone speaks next
if room comes to a state where last person stopped speaking,
then setting room to clean state is logical,
next person to start speaking is the only reference point
2023-12-03 14:39:40 +00:00
efim
acd9f4fc62 feat: custom gesture & room metrics 2023-12-02 11:30:56 +00:00
7 changed files with 117 additions and 13 deletions

View File

@@ -35,7 +35,7 @@
some-automoderation = pkgs.buildGoModule {
inherit pname version;
src = pkgs.nix-gitignore.gitignoreSource [ ] ./.;
vendorHash = "sha256-FOIDFJNBViicvcpDG3T1KYABmI5Xyrv+IdQvT2Elhjg=";
vendorHash = "sha256-ID0WG0pa9DkXTJ7aB9VywGO3R85FWkpXaaIuugnG6mg=";
preBuild = ''
${pkgs.nodePackages.tailwindcss}/bin/tailwindcss -i routes/in.css -o routes/static/out.css

12
main.go
View File

@@ -10,6 +10,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/redis/go-redis/v9"
"sunshine.industries/some-automoderation/metrics"
"sunshine.industries/some-automoderation/rooms"
"sunshine.industries/some-automoderation/routes"
"sunshine.industries/some-automoderation/sessions"
@@ -26,7 +27,10 @@ func main() {
flag.IntVar(&redisPort, "redisPort", 7777, "Port on which server should connect to redis db")
flag.Parse()
promHandler := promhttp.Handler()
metrics := metrics.SetupMetrics()
promhttp.Handler()
promHandler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{})
promMux := http.NewServeMux()
promMux.Handle("/metrics", promHandler)
@@ -40,11 +44,11 @@ func main() {
DB: 0,
})
roomsM := rooms.RedisRM { Rdb: rdb, }
sessions := sessions.RedisSM{ Rdb: rdb, }
roomsM := rooms.RedisRM{Rdb: rdb}
sessions := sessions.RedisSM{Rdb: rdb}
log.Printf("Server will start on port: %d; /metrics on %d; listening to redis on: %d\n", port, metricsPort, redisPort)
routes.RegisterRoutes(sessions, roomsM)
routes.RegisterRoutes(sessions, roomsM, &metrics)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}

43
metrics/metrics.go Normal file
View File

@@ -0,0 +1,43 @@
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
)
type MetricsContainer struct {
Registry *prometheus.Registry
LiveConnectionsGauge prometheus.Gauge
RaiseGestureCounter *prometheus.CounterVec
SpeakerCounter *prometheus.CounterVec
}
const GestureNameLabel string = "gestureString"
func SetupMetrics() MetricsContainer {
registry := prometheus.NewRegistry()
liveConnectionsGauge := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "live_connections_total",
Help: "Total amount of live SSE subscriptions to room state",
})
raiseGestureCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "raise_gesture_total",
Help: "Total amount of raised hands",
}, []string{GestureNameLabel})
speakerCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "speaker_total",
Help: "Total amount of speaker turns",
}, []string{GestureNameLabel})
registry.MustRegister(liveConnectionsGauge, raiseGestureCounter, speakerCounter,
collectors.NewGoCollector(),
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
)
return MetricsContainer{
Registry: registry,
LiveConnectionsGauge: liveConnectionsGauge,
RaiseGestureCounter: raiseGestureCounter,
SpeakerCounter: speakerCounter,
}
}

View File

@@ -53,6 +53,7 @@ func (r *Room) RaiseHand(p PersonId, gesture HandGesture) Room {
// - we should find next speaker
// - assign room.CurrentSpeaker to next speaker or to PersonId(0) to indicate that noone is speaking
// - if next speaker has gesture of higher priority - set Mark to current speaker for their gesture level
// when there is not next speaker, remove all marks
func (r *Room) ReleaseHand(p PersonId) {
// releasing a hand of a current speaker should result in selection of a new speaker
log.Printf("about to release hand of %d in %+v", p, r)
@@ -76,6 +77,7 @@ func (r *Room) ReleaseHand(p PersonId) {
if !nextSpeakerFound {
log.Printf("there is not next speaker, that's ok")
r.CurrentSpeaker = PersonId(0)
r.Marks = make(map[HandGesture]PersonId)
} else {
// searching for the next speaker
currentSpeakerGesture := handReleaseGesture

View File

@@ -20,6 +20,7 @@ var releaseHandTests = []releaseHandTest{
releasingNonSpeakerHand,
releaseToPersonWithHandAndMark,
raisingLevelSetMarksWithoutOverridingExisting,
releaseAllMarksWhenNoSpeaker,
}
func TestRoomReleaseHand(t *testing.T) {
@@ -314,3 +315,40 @@ var releaseToPersonWithHandAndMark releaseHandTest = releaseHandTest{
Marks: map[HandGesture]PersonId{},
},
}
// there is a mark for Expand on person 2, mark for Change Topic on person 3
// speaker is person 1 with Expand
// after hand release person nobody should be speaking, with all marks removed
var releaseAllMarksWhenNoSpeaker releaseHandTest = releaseHandTest{
testName: "releaseAllMarksWhenNoSpeaker",
room: Room{
Name: "test",
CurrentSpeaker: person1.Id,
Paricipants: []PersonId{
person1.Id,
person2.Id,
person3.Id,
person4.Id,
},
ParticipantHands: map[PersonId]HandGesture{
person1.Id: Expand,
},
Marks: map[HandGesture]PersonId{
Expand: person2.Id,
ChangeTopic: person3.Id,
},
},
releasingParticipantId: person1.Id,
expected: Room{
Name: "test",
CurrentSpeaker: PersonId(0),
Paricipants: []PersonId{
person1.Id,
person2.Id,
person3.Id,
person4.Id,
},
ParticipantHands: map[PersonId]HandGesture{},
Marks: map[HandGesture]PersonId{},
},
}

View File

@@ -10,6 +10,7 @@ import (
"strconv"
"strings"
"sunshine.industries/some-automoderation/metrics"
"sunshine.industries/some-automoderation/rooms"
"sunshine.industries/some-automoderation/sessions"
)
@@ -23,6 +24,7 @@ func registerPageRoutes(
templateFs *embed.FS,
sessionSM sessions.SessionManagement,
roomsM rooms.RoomManager,
metrics *metrics.MetricsContainer,
) {
http.Handle(roomPath, // ending in / captures all following path sections, i.e room name
http.StripPrefix(roomPath, roomPageRoute(templateFs, roomsM, sessionSM)))
@@ -30,15 +32,15 @@ func registerPageRoutes(
http.Handle(raiseHandPath, // ending in / captures all following path sections, i.e gesture num
authedPageMiddleware(
sessionSM,
http.StripPrefix(raiseHandPath, raiseGestureHandRoute(roomsM))))
http.StripPrefix(raiseHandPath, raiseGestureHandRoute(roomsM, metrics))))
http.Handle("/rooms/releaseHand",
authedPageMiddleware(sessionSM, releaseHandRoute(roomsM)))
authedPageMiddleware(sessionSM, releaseHandRoute(roomsM, metrics)))
http.Handle(subscribeRoomPath,
authedPageMiddleware(
sessionSM,
http.StripPrefix(subscribeRoomPath, streamingRoomStates(templateFs, roomsM))))
http.StripPrefix(subscribeRoomPath, streamingRoomStates(templateFs, roomsM, metrics))))
http.HandleFunc("/rooms/preview-templates", roomTemplatesPreview(templateFs))
@@ -48,8 +50,11 @@ func registerPageRoutes(
func streamingRoomStates(
templateFs *embed.FS,
roomsM rooms.RoomManager,
metrics *metrics.MetricsContainer,
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
metrics.LiveConnectionsGauge.Inc()
defer metrics.LiveConnectionsGauge.Dec()
r.ParseForm()
roomName := r.FormValue("roomName")
defer log.Printf("/rooms/subscribe/%s stream ended\n", roomName)
@@ -112,6 +117,7 @@ func streamingRoomStates(
// TODO should return control for raised state
func raiseGestureHandRoute(
roomsM rooms.RoomManager,
metrics *metrics.MetricsContainer,
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
gestureInd, err := strconv.Atoi(r.URL.Path)
@@ -120,6 +126,7 @@ func raiseGestureHandRoute(
log.Printf("/rooms/raiseGesture error %s gettin hand symbol index from path %s\n", err, r.URL.Path)
return
}
metrics.RaiseGestureCounter.WithLabelValues(selectedGesture.String()).Inc()
log.Printf("/rooms/raiseGesture successfully got gesture %d : %s", selectedGesture, selectedGesture.String())
session, found := getContextSession(r.Context())
if !found {
@@ -129,6 +136,10 @@ func raiseGestureHandRoute(
}
var room rooms.Room
err = roomsM.Update(r.Context(), session.RoomId, func(fromRoom rooms.Room) (toRoom rooms.Room) {
if fromRoom.CurrentSpeaker == rooms.PersonId(0) {
// room had no speaker, need count new speaker turn
metrics.SpeakerCounter.WithLabelValues(selectedGesture.String()).Inc()
}
toRoom = fromRoom.RaiseHand(session.PersonId, selectedGesture)
room = toRoom
return toRoom
@@ -167,6 +178,7 @@ func raiseGestureHandRoute(
// TODO should return lowered control for passed hand gesture, i guess optional
func releaseHandRoute(
roomsM rooms.RoomManager,
metrics *metrics.MetricsContainer,
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, found := getContextSession(r.Context())
@@ -179,6 +191,10 @@ func releaseHandRoute(
err := roomsM.Update(r.Context(), session.RoomId, func(fromRoom rooms.Room) (toRoom rooms.Room) {
toRoom = fromRoom
toRoom.ReleaseHand(session.PersonId)
if fromRoom.CurrentSpeaker != toRoom.CurrentSpeaker && toRoom.CurrentSpeaker != rooms.PersonId(0) {
gesture := toRoom.ParticipantHands[toRoom.CurrentSpeaker]
metrics.SpeakerCounter.WithLabelValues(gesture.String()).Inc()
}
return toRoom
})
if err != nil {

View File

@@ -4,6 +4,7 @@ import (
"embed"
"net/http"
"sunshine.industries/some-automoderation/metrics"
"sunshine.industries/some-automoderation/rooms"
"sunshine.industries/some-automoderation/sessions"
)
@@ -14,7 +15,7 @@ var templateFs embed.FS
//go:embed static
var staticFilesFs embed.FS
func RegisterRoutes(sessionsM sessions.SessionManagement, rooms rooms.RoomManager) {
func RegisterRoutes(sessionsM sessions.SessionManagement, rooms rooms.RoomManager, metrics *metrics.MetricsContainer) {
// login page
registerLoginRoutes(&templateFs, sessionsM, rooms)
@@ -22,7 +23,7 @@ func RegisterRoutes(sessionsM sessions.SessionManagement, rooms rooms.RoomManage
http.Handle("/", indexPageRoute(&templateFs, sessionsM, rooms))
// main conversation room page
registerPageRoutes(&templateFs, sessionsM, rooms)
registerPageRoutes(&templateFs, sessionsM, rooms, metrics)
// static resources route
http.Handle("/static/",