pico
created pr with
103.1
added 103.2
1: 66cf9b9 = 1: 66cf9b9 feat(pipe): add pipe_monitors table
2: 83b0c70 = 2: 83b0c70 chore(pipe): add new db methods
3: 43aa4a1 = 3: 43aa4a1 chore(pipe): add db impl
4: a75186b = 4: a75186b chore(pipe): add tests for monitoring
5: 9c7fcda = 5: 9c7fcda feat(pipe): add monitor cli
6: 2b86dec = 6: 2b86dec feat(pipe): status and rss commands
7: 8df2aa2 = 7: 8df2aa2 feat(pipe): monitor pub and pipe cmd
8: 11a5bce = 8: 11a5bce chore(pipe): monitor help text
9: 670350c = 9: 670350c refactor(pipe): monitor pipes on throttle interval
10: ade2b83 = 10: ade2b83 fix: window and ping fixes
11: 68f5cd1 = 11: 68f5cd1 chore(pipe): add tests
-: ------- > 12: 74fb40f chore(pipe): add logging stmts
-: ------- > 13: c422818 refactor(pipe): create pipe monitors history table
-: ------- > 14: e0c3a88 fix(pipe): only update monitor if within window
-: ------- > 15: 9a8fbde chore(pipe): implement monitor history db interface
-: ------- > 16: f168507 feat(pipe): record historical monitors
-: ------- > 17: 16df77f feat(pipe): monitor calculate uptime
-: ------- > 18: 10ffdf1 feat(pipe): uptime cli command
-: ------- > 19: 8231ac5 chore(pipe): monitor history stubs
-: ------- > 20: 65a63a9 fix(pipe): uptime cmd
added 103.3
3: 43aa4a1 ! 1: 603ca6e chore(pubsub): add more tests
1: 66cf9b9 < -: ------- feat(pipe): add pipe_monitors table
2: 83b0c70 < -: ------- chore(pipe): add new db methods
4: a75186b ! 2: 17e00b2 feat(pubsub): round robin
13: c422818 ! 3: e3136bd fix(pubsub): check for eof before processing and skip empty byte reads
-: ------- > 4: 9a6d19e fix: rr
5: 9c7fcda < -: ------- feat(pipe): add monitor cli
16: f168507 ! 5: 5b3f3a1 fix: sending 0 byte read
6: 2b86dec < -: ------- feat(pipe): status and rss commands
11: 68f5cd1 ! 6: 4fff471 refactor: fixes
7: 8df2aa2 < -: ------- feat(pipe): monitor pub and pipe cmd
14: e0c3a88 ! 7: d0dfc85 chore: SetDispatch on Broker
8: 11a5bce < -: ------- chore(pipe): monitor help text
9: 670350c < -: ------- refactor(pipe): monitor pipes on throttle interval
10: ade2b83 < -: ------- fix: window and ping fixes
12: 74fb40f < -: ------- chore(pipe): add logging stmts
15: 9a8fbde < -: ------- chore(pipe): implement monitor history db interface
17: 16df77f < -: ------- feat(pipe): monitor calculate uptime
18: 10ffdf1 < -: ------- feat(pipe): uptime cli command
19: 8231ac5 < -: ------- chore(pipe): monitor history stubs
20: 65a63a9 < -: ------- fix(pipe): uptime cmd
cmds
checkout latest patchset:
ssh pr.pico.sh print 103 | git am -3checkout any patchset in a patch request:
ssh pr.pico.sh print 103.[rev] | git am -3add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 103
Patchset
103.1
feat(pipe): add pipe_monitors table
Eric Bower
chore(pipe): add new db methods
2025-12-26T21:02:39ZEric Bower
chore(pipe): add db impl
2025-12-26T21:24:18ZEric Bower
chore(pipe): add tests for monitoring
2025-12-26T21:37:49ZEric Bower
feat(pipe): add monitor cli
2025-12-28T14:41:40ZEric Bower
feat(pipe): status and rss commands
2025-12-28T14:44:04ZEric Bower
→ feat(pipe): monitor pub and pipe cmd
2025-12-28T14:55:12ZEric Bower
chore(pipe): monitor help text
2025-12-28T15:26:40ZEric Bower
refactor(pipe): monitor pipes on throttle interval
2025-12-28T15:40:57ZEric Bower
fix: window and ping fixes
2025-12-28T16:22:23ZEric Bower
chore(pipe): add tests
2026-01-03T02:26:40ZEric Bower
2026-01-03T03:50:20Z
feat(pipe): monitor pub and pipe cmd
Eric Bower
2025-12-28T15:26:40ZSemantic diff summary
1 added,
8 modified,
0 signature changed,
0 removed
across 2 analyzed files
+43
-4
pkg/apps/pipe/cli.go
#
| ... | ... | @@ -320,12 +322,21 @@ func (handler *CliHandler) monitor(cmd *CliCmd, user *db.User) error { | |
| 320 | 322 | return fmt.Errorf("topic is required") | |
| 321 | 323 | } | |
| 322 | 324 | ||
| 325 | + | // Resolve to fully qualified topic name | |
| 326 | + | result := resolveTopic(TopicResolveInput{ | |
| 327 | + | UserName: cmd.userName, | |
| 328 | + | Topic: topic, | |
| 329 | + | IsAdmin: cmd.isAdmin, | |
| 330 | + | IsPublic: false, | |
| 331 | + | }) | |
| 332 | + | resolvedTopic := result.Name | |
| 333 | + | ||
| 323 | 334 | if *del { | |
| 324 | - | err := handler.DBPool.RemovePipeMonitor(user.ID, topic) | |
| 335 | + | err := handler.DBPool.RemovePipeMonitor(user.ID, resolvedTopic) | |
| 325 | 336 | if err != nil { | |
| 326 | 337 | return fmt.Errorf("failed to delete monitor: %w", err) | |
| 327 | 338 | } | |
| 328 | - | _, _ = fmt.Fprintf(cmd.sesh, "monitor deleted: %s\r\n", topic) | |
| 339 | + | _, _ = fmt.Fprintf(cmd.sesh, "monitor deleted: %s\r\n", resolvedTopic) | |
| 329 | 340 | return nil | |
| 330 | 341 | } | |
| 331 | 342 |
| ... | ... | @@ -348,12 +359,12 @@ func (handler *CliHandler) monitor(cmd *CliCmd, user *db.User) error { | |
| 348 | 359 | } | |
| 349 | 360 | ||
| 350 | 361 | winEnd := time.Now().Add(dur) | |
| 351 | - | err = handler.DBPool.UpsertPipeMonitor(user.ID, topic, dur, &winEnd) | |
| 362 | + | err = handler.DBPool.UpsertPipeMonitor(user.ID, resolvedTopic, dur, &winEnd) | |
| 352 | 363 | if err != nil { | |
| 353 | 364 | return fmt.Errorf("failed to create monitor: %w", err) | |
| 354 | 365 | } | |
| 355 | 366 | ||
| 356 | - | _, _ = fmt.Fprintf(cmd.sesh, "monitor created: %s (window: %s)\r\n", topic, dur) | |
| 367 | + | _, _ = fmt.Fprintf(cmd.sesh, "monitor created: %s (window: %s)\r\n", resolvedTopic, dur) | |
| 357 | 368 | return nil | |
| 358 | 369 | } | |
| 359 | 370 |
| ... | ... | @@ -672,9 +683,35 @@ func (handler *CliHandler) pub(cmd *CliCmd, topic string, clientID string) error | |
| 672 | 683 | return err | |
| 673 | 684 | } | |
| 674 | 685 | ||
| 686 | + | handler.updateMonitor(cmd, name) | |
| 687 | + | ||
| 675 | 688 | return nil | |
| 676 | 689 | } | |
| 677 | 690 | ||
| 691 | + | func (handler *CliHandler) updateMonitor(cmd *CliCmd, topic string) { | |
| 692 | + | if cmd.user == nil { | |
| 693 | + | return | |
| 694 | + | } | |
| 695 | + | ||
| 696 | + | monitor, err := handler.DBPool.FindPipeMonitorByTopic(cmd.user.ID, topic) | |
| 697 | + | if err != nil || monitor == nil { | |
| 698 | + | return | |
| 699 | + | } | |
| 700 | + | ||
| 701 | + | now := time.Now() | |
| 702 | + | if err := handler.DBPool.UpdatePipeMonitorLastPing(cmd.user.ID, topic, &now); err != nil { | |
| 703 | + | handler.Logger.Error("failed to update monitor last_ping", "err", err, "topic", topic) | |
| 704 | + | } | |
| 705 | + | ||
| 706 | + | // Advance window if current time is past window_end | |
| 707 | + | if monitor.WindowEnd != nil && now.After(*monitor.WindowEnd) { | |
| 708 | + | nextWindow := monitor.GetNextWindow() | |
| 709 | + | if err := handler.DBPool.UpsertPipeMonitor(cmd.user.ID, topic, monitor.WindowDur, nextWindow); err != nil { | |
| 710 | + | handler.Logger.Error("failed to advance monitor window", "err", err, "topic", topic) | |
| 711 | + | } | |
| 712 | + | } | |
| 713 | + | } | |
| 714 | + | ||
| 678 | 715 | func (handler *CliHandler) sub(cmd *CliCmd, topic string, clientID string) error { | |
| 679 | 716 | subCmd := flagSet("sub", cmd.sesh) | |
| 680 | 717 | access := subCmd.String("a", "", "Comma separated list of pico usernames or ssh-key fingerprints to allow access to a topic") |
+13
-13
pkg/apps/pipe/ssh_test.go
#
| ... | ... | @@ -1505,8 +1505,8 @@ func TestMonitor_CreateMonitor(t *testing.T) { | |
| 1505 | 1505 | t.Errorf("authenticated user should not get access denied, got: %s", output) | |
| 1506 | 1506 | } | |
| 1507 | 1507 | ||
| 1508 | - | // Verify monitor was created in DB | |
| 1509 | - | monitor, err := server.DBPool.FindPipeMonitorByTopic("alice-id", "pico-uptime") | |
| 1508 | + | // Verify monitor was created in DB (topic is stored with user prefix) | |
| 1509 | + | monitor, err := server.DBPool.FindPipeMonitorByTopic("alice-id", "alice/pico-uptime") | |
| 1510 | 1510 | if err != nil { | |
| 1511 | 1511 | t.Fatalf("monitor should exist in DB: %v", err) | |
| 1512 | 1512 | } |
| ... | ... | @@ -1515,7 +1515,7 @@ func TestMonitor_CreateMonitor(t *testing.T) { | |
| 1515 | 1515 | t.Errorf("expected window duration 24h, got: %v", monitor.WindowDur) | |
| 1516 | 1516 | } | |
| 1517 | 1517 | ||
| 1518 | - | if !strings.Contains(output, "pico-uptime") || !strings.Contains(output, "24h") { | |
| 1518 | + | if !strings.Contains(output, "alice/pico-uptime") || !strings.Contains(output, "24h") { | |
| 1519 | 1519 | t.Errorf("output should confirm monitor creation, got: %s", output) | |
| 1520 | 1520 | } | |
| 1521 | 1521 | } |
| ... | ... | @@ -1545,8 +1545,8 @@ func TestMonitor_UpdateMonitor(t *testing.T) { | |
| 1545 | 1545 | t.Logf("update command completed: %v", err) | |
| 1546 | 1546 | } | |
| 1547 | 1547 | ||
| 1548 | - | // Verify monitor was updated | |
| 1549 | - | monitor, err := server.DBPool.FindPipeMonitorByTopic("alice-id", "my-cron") | |
| 1548 | + | // Verify monitor was updated (topic is stored with user prefix) | |
| 1549 | + | monitor, err := server.DBPool.FindPipeMonitorByTopic("alice-id", "alice/my-cron") | |
| 1550 | 1550 | if err != nil { | |
| 1551 | 1551 | t.Fatalf("monitor should exist in DB: %v", err) | |
| 1552 | 1552 | } |
| ... | ... | @@ -1579,8 +1579,8 @@ func TestMonitor_DeleteMonitor(t *testing.T) { | |
| 1579 | 1579 | t.Logf("create command completed: %v", err) | |
| 1580 | 1580 | } | |
| 1581 | 1581 | ||
| 1582 | - | // Verify it exists | |
| 1583 | - | _, err = server.DBPool.FindPipeMonitorByTopic("alice-id", "to-delete") | |
| 1582 | + | // Verify it exists (topic is stored with user prefix) | |
| 1583 | + | _, err = server.DBPool.FindPipeMonitorByTopic("alice-id", "alice/to-delete") | |
| 1584 | 1584 | if err != nil { | |
| 1585 | 1585 | t.Fatalf("monitor should exist before deletion: %v", err) | |
| 1586 | 1586 | } |
| ... | ... | @@ -1591,8 +1591,8 @@ func TestMonitor_DeleteMonitor(t *testing.T) { | |
| 1591 | 1591 | t.Logf("delete command completed: %v", err) | |
| 1592 | 1592 | } | |
| 1593 | 1593 | ||
| 1594 | - | // Verify it's gone | |
| 1595 | - | _, err = server.DBPool.FindPipeMonitorByTopic("alice-id", "to-delete") | |
| 1594 | + | // Verify it's gone (topic is stored with user prefix) | |
| 1595 | + | _, err = server.DBPool.FindPipeMonitorByTopic("alice-id", "alice/to-delete") | |
| 1596 | 1596 | if err == nil { | |
| 1597 | 1597 | t.Errorf("monitor should be deleted from DB") | |
| 1598 | 1598 | } |
| ... | ... | @@ -1873,10 +1873,10 @@ func TestPub_UpdatesMonitorLastPing(t *testing.T) { | |
| 1873 | 1873 | user := GenerateUser("alice") | |
| 1874 | 1874 | RegisterUserWithServer(server, user) | |
| 1875 | 1875 | ||
| 1876 | - | // Create a monitor first | |
| 1876 | + | // Create a monitor first (topic is stored with user prefix) | |
| 1877 | 1877 | now := time.Now() | |
| 1878 | 1878 | windowEnd := now.Add(1 * time.Hour) | |
| 1879 | - | _ = server.DBPool.UpsertPipeMonitor("alice-id", "ping-test", 1*time.Hour, &windowEnd) | |
| 1879 | + | _ = server.DBPool.UpsertPipeMonitor("alice-id", "alice/ping-test", 1*time.Hour, &windowEnd) | |
| 1880 | 1880 | ||
| 1881 | 1881 | subClient, err := user.NewClient() | |
| 1882 | 1882 | if err != nil { |
| ... | ... | @@ -1909,8 +1909,8 @@ func TestPub_UpdatesMonitorLastPing(t *testing.T) { | |
| 1909 | 1909 | t.Logf("pub command completed: %v", err) | |
| 1910 | 1910 | } | |
| 1911 | 1911 | ||
| 1912 | - | // Verify last_ping was updated | |
| 1913 | - | monitor, err := server.DBPool.FindPipeMonitorByTopic("alice-id", "ping-test") | |
| 1912 | + | // Verify last_ping was updated (topic is stored with user prefix) | |
| 1913 | + | monitor, err := server.DBPool.FindPipeMonitorByTopic("alice-id", "alice/ping-test") | |
| 1914 | 1914 | if err != nil { | |
| 1915 | 1915 | t.Fatalf("monitor should exist: %v", err) | |
| 1916 | 1916 | } |