Compare commits
13 Commits
faae75d5f4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 439c481c60 | |||
| 0a58f85ca3 | |||
| 141c0e5100 | |||
| 9f3de791f9 | |||
| 8d3db70e01 | |||
| 831fed82f7 | |||
| 43887bb447 | |||
| 0b9b790986 | |||
| ae54bee23b | |||
| 84a8897bf0 | |||
| 2e339cff36 | |||
| 52c00adfe7 | |||
| 1be18b80d2 |
+15
@@ -0,0 +1,15 @@
|
||||
FROM golang:1.26-trixie AS builder
|
||||
WORKDIR /go/src/
|
||||
COPY ./go.* ./
|
||||
COPY ./cmd ./cmd/
|
||||
COPY ./.vendor ./.vendor/
|
||||
ENV CGO_ENABLED=0
|
||||
RUN go build \
|
||||
-o /go/bin/binary \
|
||||
-mod=readonly \
|
||||
-ldflags="-extldflags '-static'" \
|
||||
./cmd/*/
|
||||
FROM debian:trixie-slim AS runtime
|
||||
COPY --from=builder /go/bin/binary /bin/binary
|
||||
CMD []
|
||||
ENTRYPOINT ["/bin/binary"]
|
||||
@@ -1,19 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"gitea.bel.blue/bel/with"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := with.Context(run); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run(ctx context.Context) error {
|
||||
log.Fatal("RUN")
|
||||
return ctx.Err()
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<header>
|
||||
<script>
|
||||
function onload() {
|
||||
const results = [
|
||||
{{ range $v := query
|
||||
`
|
||||
SELECT
|
||||
start AS start
|
||||
, CASE
|
||||
WHEN ms < 50 AND ok THEN
|
||||
'okay'
|
||||
WHEN ms < 750 AND ok THEN
|
||||
'slow'
|
||||
ELSE
|
||||
'bad'
|
||||
END AS ok
|
||||
, ms AS ms
|
||||
FROM
|
||||
"pinger_pings"
|
||||
ORDER BY start DESC
|
||||
LIMIT 1000
|
||||
`
|
||||
-}}
|
||||
[
|
||||
{{.ok}}==="okay"
|
||||
? "🟢"
|
||||
: {{.ok}}==="slow"
|
||||
? "🟠"
|
||||
: "🔴",
|
||||
`${Math.round((new Date() - 1000*{{.start}})/1000.0)}s ago`,
|
||||
new Date(1000*{{.start}}),
|
||||
"{{.ms}}ms",
|
||||
],
|
||||
{{ end }}
|
||||
]
|
||||
document.getElementById("draw").innerHTML = results.map((r) => `<br>${JSON.stringify(r)}`).join("\n");
|
||||
}
|
||||
</script>
|
||||
</header>
|
||||
<body onload="onload()">
|
||||
<div id="draw">
|
||||
</div>
|
||||
</body>
|
||||
<footer>
|
||||
</footer>
|
||||
</html>
|
||||
@@ -0,0 +1,261 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"context"
|
||||
"database/sql"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.bel.blue/bel/with"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := with.Context(run); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type S struct {
|
||||
*sql.DB
|
||||
}
|
||||
|
||||
func run(ctx context.Context) error {
|
||||
return with.Sqlite(ctx, "/tmp/pinger.db", func(db *sql.DB) error {
|
||||
s := S{
|
||||
DB: db,
|
||||
}
|
||||
if err := s.init(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
defer wg.Wait()
|
||||
for _, foo := range []func(context.Context) error{
|
||||
s.pingAndRecord,
|
||||
s.collectGarbage,
|
||||
s.listen,
|
||||
} {
|
||||
foo := foo
|
||||
wg.Go(func() {
|
||||
if err := foo(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
return ctx.Err()
|
||||
})
|
||||
}
|
||||
|
||||
func (s S) init(ctx context.Context) error {
|
||||
for _, q := range []string{
|
||||
`CREATE TABLE IF NOT EXISTS "pinger_to" (
|
||||
id NUMBER NOT NULL UNIQUE
|
||||
, addr TEXT NOT NULL UNIQUE
|
||||
)`,
|
||||
`CREATE TABLE IF NOT EXISTS "pinger_pings" (
|
||||
start TIMESTAMP NOT NULL
|
||||
, ms NUMBER NOT NULL
|
||||
, ok BOOLEAN NOT NULL DEFAULT false
|
||||
, to_id NUMBER
|
||||
, FOREIGN KEY (to_id) REFERENCES "pinger_to"(id)
|
||||
)`,
|
||||
//`ALTER TABLE "pinger_pings" SET UNLOGGED`,
|
||||
} {
|
||||
if _, err := s.ExecContext(ctx, q); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
//go:embed index.html.tmpl
|
||||
var IndexTemplate string
|
||||
|
||||
func (s S) listen(ctx context.Context) error {
|
||||
t := template.New("template")
|
||||
t.Funcs(map[string]any{
|
||||
"query": func(q string) ([]map[string]any, error) {
|
||||
rows, err := s.QueryContext(ctx, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
columns, err := rows.Columns()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := []map[string]any{}
|
||||
for rows.Next() {
|
||||
var scanners []any
|
||||
for range columns {
|
||||
var a any
|
||||
scanners = append(scanners, &a)
|
||||
}
|
||||
if err := rows.Scan(scanners...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := map[string]any{}
|
||||
for i, column := range columns {
|
||||
result[column] = scanners[i]
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
return results, rows.Err()
|
||||
},
|
||||
})
|
||||
t, err := t.Parse(IndexTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
server := &http.Server{
|
||||
Addr: ":10000",
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := t.Execute(w, map[string]any{
|
||||
"DB": s.DB,
|
||||
}); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}),
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
server.Close()
|
||||
}()
|
||||
|
||||
if err := server.ListenAndServe(); err != nil && ctx.Err() == nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s S) collectGarbage(ctx context.Context) error {
|
||||
c := time.NewTicker(time.Hour)
|
||||
defer c.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.C:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
if _, err := s.ExecContext(ctx, `
|
||||
DELETE FROM "pinger_pings"
|
||||
WHERE
|
||||
start < $1
|
||||
AND (
|
||||
ms < 100
|
||||
OR ok
|
||||
)
|
||||
`, time.Now().Add(-1*2*24*time.Hour).Unix()); err != nil {
|
||||
log.Println("!", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s S) pingAndRecord(ctx context.Context) error {
|
||||
ctx, can := context.WithCancel(ctx)
|
||||
defer can()
|
||||
|
||||
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
||||
target := fs.String("t", "google.com", "target")
|
||||
port := fs.String("p", "80", "target port")
|
||||
interval := fs.Duration("i", time.Second, "between conns")
|
||||
if err := fs.Parse(os.Args[1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%s", *target, *port)
|
||||
if _, err := s.ExecContext(ctx, `
|
||||
INSERT INTO "pinger_to" (id, addr)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (addr) DO NOTHING
|
||||
`, rand.Int(), addr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var toId int
|
||||
if row := s.QueryRowContext(ctx, `
|
||||
SELECT id
|
||||
FROM "pinger_to"
|
||||
WHERE addr=$1
|
||||
`, addr); row.Err() != nil {
|
||||
return row.Err()
|
||||
} else if err := row.Scan(&toId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type record struct {
|
||||
start time.Time
|
||||
ms time.Duration
|
||||
ok bool
|
||||
}
|
||||
records := make(chan record, 100)
|
||||
defer close(records)
|
||||
go func() {
|
||||
for record := range records {
|
||||
if _, err := s.ExecContext(ctx, `
|
||||
INSERT INTO "pinger_pings"
|
||||
(start, ms, ok, to_id)
|
||||
VALUES
|
||||
($1, $2, $3, $4)
|
||||
`, record.start.Unix(), record.ms.Milliseconds(), record.ok, toId); err != nil {
|
||||
log.Println("!", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
c := time.NewTicker(*interval)
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
c.Stop()
|
||||
}()
|
||||
|
||||
dialer := &net.Dialer{
|
||||
Timeout: time.Second,
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.C:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
func() {
|
||||
ctx, can := context.WithTimeout(ctx, *interval)
|
||||
defer can()
|
||||
start := time.Now()
|
||||
conn, err := dialer.DialContext(ctx, "tcp", addr)
|
||||
select {
|
||||
case records <- record{
|
||||
start: start,
|
||||
ms: time.Since(start),
|
||||
ok: err == nil,
|
||||
}:
|
||||
default:
|
||||
}
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
} else {
|
||||
log.Println("!", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return ctx.Err()
|
||||
}
|
||||
@@ -5,13 +5,17 @@ go 1.25.0
|
||||
require gitea.bel.blue/bel/with v0.0.0-20260527141310-eadcabb7fe54
|
||||
|
||||
require (
|
||||
github.com/digineo/go-logwrap v0.0.0-20181106161722-a178c58ea3f0 // indirect
|
||||
github.com/digineo/go-ping v1.2.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/lib/pq v1.11.2 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e // indirect
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
modernc.org/libc v1.67.6 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
github.com/digineo/go-logwrap v0.0.0-20181106161722-a178c58ea3f0 h1:OT/LKmj81wMymnWXaKaKBR9n1vPlu+GC0VVKaZP6kzs=
|
||||
github.com/digineo/go-logwrap v0.0.0-20181106161722-a178c58ea3f0/go.mod h1:DmqdumeAKGQNU5E8MN0ruT5ZGx8l/WbAsMbXCXcSEts=
|
||||
github.com/digineo/go-ping v1.2.0 h1:/9vEsoCRtQvol5vRMA2pE8guuhUVDSJB2ok2nKuJJbA=
|
||||
github.com/digineo/go-ping v1.2.0/go.mod h1:cXJTVTs7mthQ41c/nWykPYuhlDQwCN0ba5LyOnyYv8Y=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
@@ -14,10 +18,14 @@ github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOF
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e h1:nt2877sKfojlHCTOBXbpWjBkuWKritFaGIfgQwbQUls=
|
||||
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e/go.mod h1:B4+Kq1u5FlULTjFSM707Q6e/cOHFv0z/6QRoxubDIQ8=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
||||
Reference in New Issue
Block a user