commit c0d87f1c0ca402c5758749bbc0e67bc4e5e2a3fd
Author: usbharu
Date: Sat Feb 1 16:26:12 2025 +0900
first commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..10165ac
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/test.env
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..cc5c502
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# デフォルトの無視対象ファイル
+/shelf/
+/workspace.xml
+# エディターベースの HTTP クライアントリクエスト
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..08c396b
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+misskey-exporter
\ No newline at end of file
diff --git a/.idea/misskey-exporter.iml b/.idea/misskey-exporter.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/misskey-exporter.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..9854b96
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..e0d3ebc
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,13 @@
+# ステージ1
+FROM golang:1.23.5-alpine3.21 AS go
+WORKDIR /app
+COPY go.mod go.sum main.go collector.go ./
+RUN go mod download \
+&& go build -o main .
+
+# ステージ2
+FROM alpine:3.21
+WORKDIR /app
+COPY --from=go /app/main .
+USER 1001
+CMD [ "/app/main" ]
\ No newline at end of file
diff --git a/collector.go b/collector.go
new file mode 100644
index 0000000..aee6b5a
--- /dev/null
+++ b/collector.go
@@ -0,0 +1,114 @@
+package main
+
+import (
+ "bytes"
+ "github.com/sirupsen/logrus"
+ "github.com/yitsushi/go-misskey/services/notes/timeline"
+ "net/http"
+ "time"
+)
+
+func collectJobqueue() {
+ for {
+ time.Sleep(1 * time.Second)
+ stats, err := client.Admin().Queue().Stats()
+ if err != nil {
+ logrus.Warning(err)
+ continue
+ }
+ misskeyJobQueueJobsCount.WithLabelValues("deliver", "waiting").Set(float64(stats.Deliver.Waiting))
+ misskeyJobQueueJobsCount.WithLabelValues("deliver", "active").Set(float64(stats.Deliver.Active))
+ misskeyJobQueueJobsCount.WithLabelValues("deliver", "completed").Set(float64(stats.Deliver.Completed))
+ misskeyJobQueueJobsCount.WithLabelValues("deliver", "failed").Set(float64(stats.Deliver.Failed))
+ misskeyJobQueueJobsCount.WithLabelValues("deliver", "delayed").Set(float64(stats.Deliver.Delayed))
+ misskeyJobQueueJobsCount.WithLabelValues("deliver", "paused").Set(float64(stats.Deliver.Paused))
+ misskeyJobQueueJobsCount.WithLabelValues("inbox", "waiting").Set(float64(stats.Inbox.Waiting))
+ misskeyJobQueueJobsCount.WithLabelValues("inbox", "active").Set(float64(stats.Inbox.Active))
+ misskeyJobQueueJobsCount.WithLabelValues("inbox", "completed").Set(float64(stats.Inbox.Completed))
+ misskeyJobQueueJobsCount.WithLabelValues("inbox", "failed").Set(float64(stats.Inbox.Failed))
+ misskeyJobQueueJobsCount.WithLabelValues("inbox", "delayed").Set(float64(stats.Inbox.Delayed))
+ misskeyJobQueueJobsCount.WithLabelValues("inbox", "paused").Set(float64(stats.Inbox.Paused))
+ misskeyJobQueueJobsCount.WithLabelValues("db", "waiting").Set(float64(stats.DB.Waiting))
+ misskeyJobQueueJobsCount.WithLabelValues("db", "active").Set(float64(stats.DB.Active))
+ misskeyJobQueueJobsCount.WithLabelValues("db", "completed").Set(float64(stats.DB.Completed))
+ misskeyJobQueueJobsCount.WithLabelValues("db", "failed").Set(float64(stats.DB.Failed))
+ misskeyJobQueueJobsCount.WithLabelValues("db", "delayed").Set(float64(stats.DB.Delayed))
+ misskeyJobQueueJobsCount.WithLabelValues("db", "paused").Set(float64(stats.DB.Paused))
+ misskeyJobQueueJobsCount.WithLabelValues("objectstorage", "waiting").Set(float64(stats.ObjectStorage.Waiting))
+ misskeyJobQueueJobsCount.WithLabelValues("objectstorage", "active").Set(float64(stats.ObjectStorage.Active))
+ misskeyJobQueueJobsCount.WithLabelValues("objectstorage", "completed").Set(float64(stats.ObjectStorage.Completed))
+ misskeyJobQueueJobsCount.WithLabelValues("objectstorage", "failed").Set(float64(stats.ObjectStorage.Failed))
+ misskeyJobQueueJobsCount.WithLabelValues("objectstorage", "delayed").Set(float64(stats.ObjectStorage.Delayed))
+ misskeyJobQueueJobsCount.WithLabelValues("objectstorage", "paused").Set(float64(stats.ObjectStorage.Paused))
+ }
+}
+
+func collectPing() {
+ for {
+ time.Sleep(5 * time.Second)
+ var buf = bytes.NewBuffer([]byte(`{}`))
+ start := time.Now()
+ resp, err := http.Post("https://"+endpoint+"/api/ping", "application/json", buf)
+ if err != nil {
+ logrus.Warning(err)
+ continue
+ }
+ err = resp.Body.Close()
+ if err != nil {
+ logrus.Warning(err)
+ continue
+ }
+ misskeyPingResponseCode.Set(float64(resp.StatusCode))
+ misskeyPingResponseTime.Observe(float64(time.Since(start).Milliseconds()) / 1000)
+ misskeyPingResponseTimeRaw.Set(float64(time.Since(start).Milliseconds()) / 1000)
+ }
+}
+
+func collectStats() {
+ for {
+ time.Sleep(30 * time.Second)
+ stats, err := client.Meta().Stats()
+ if err != nil {
+ continue
+ }
+ misskeyNotesCount.Set(float64(stats.NotesCount))
+ misskeyUsersCount.Set(float64(stats.UsersCount))
+ misskeyOriginalNotesCount.Set(float64(stats.OriginalNotesCount))
+ misskeyOriginalUsersCount.Set(float64(stats.OriginalUsersCount))
+ }
+}
+
+func collectTimeline() {
+ for {
+ time.Sleep(30 * time.Second)
+ go func() {
+ start := time.Now()
+ global, err := client.Notes().Timeline().Global(timeline.GlobalRequest{Limit: 10})
+ if err != nil {
+ logrus.Warning(err)
+ return
+ }
+ misskeyGlobalTimelineResponseTime.Observe(float64(time.Since(start).Milliseconds()) / 1000)
+ misskeyGlobalTimelineResponseTimeRaw.Set(float64(time.Since(start).Milliseconds()) / 1000)
+ if len(global) != 0 {
+ misskeyGlobalTimelineLastNotePublished.Set(float64(global[0].CreatedAt.UnixMilli()))
+
+ }
+ }()
+ go func() {
+ start := time.Now()
+ global, err := client.Notes().Timeline().Local(timeline.LocalRequest{Limit: 10})
+ if err != nil {
+ logrus.Warning(err)
+ return
+ }
+ misskeyLocalTimelineResponseTime.Observe(float64(time.Since(start).Milliseconds()) / 1000)
+ misskeyLocalTimelineResponseTimeRaw.Set(float64(time.Since(start).Milliseconds()) / 1000)
+ if len(global) != 0 {
+ misskeyLocalTimelineLastNotePublished.Set(float64(global[0].CreatedAt.UnixMilli()))
+
+ }
+ }()
+
+ }
+}
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 0000000..c7a95df
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,22 @@
+services:
+ misskey-exporter:
+ env_file:
+ - test.env
+ build: .
+ ports:
+ - "8080:8080"
+
+ prometheus:
+ image: prom/prometheus
+ volumes:
+ - ./prometheus.yml:/etc/prometheus/prometheus.yml
+ ports:
+ - 9090:9090
+ grafana:
+ image: grafana/grafana-enterprise
+ ports:
+ - '3011:3000'
+ volumes:
+ - 'grafana_storage:/var/lib/grafana'
+volumes:
+ grafana_storage: { }
\ No newline at end of file
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..b4f542f
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,22 @@
+module misskey-exporter
+
+go 1.23
+
+require (
+ github.com/sirupsen/logrus v1.7.0
+ github.com/yitsushi/go-misskey v1.1.6
+)
+
+require (
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/klauspost/compress v1.17.9 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/prometheus/client_golang v1.20.5 // indirect
+ github.com/prometheus/client_model v0.6.1 // indirect
+ github.com/prometheus/common v0.55.0 // indirect
+ github.com/prometheus/procfs v0.15.1 // indirect
+ golang.org/x/net v0.26.0 // indirect
+ golang.org/x/sys v0.22.0 // indirect
+ google.golang.org/protobuf v1.34.2 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..801885b
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,40 @@
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
+github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
+github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
+github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
+github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
+github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
+github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
+github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/yitsushi/go-misskey v1.1.6 h1:aMfu7N9RzS2JO2QP9TPBXQyvudwU7NTr6jiOeUcho3Q=
+github.com/yitsushi/go-misskey v1.1.6/go.mod h1:FeLNMTVebkWTbjRt5X1YqkKc61dOjgTy2hyUDaOC+zc=
+golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
+golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
+golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
+gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..1095eb6
--- /dev/null
+++ b/main.go
@@ -0,0 +1,163 @@
+package main
+
+import (
+ "fmt"
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+ "github.com/sirupsen/logrus"
+ "github.com/yitsushi/go-misskey"
+ "net/http"
+ "os"
+)
+
+//TIP
To run your code, right-click the code and select Run.
Alternatively, click
+// the icon in the gutter and select the Run menu item from here.
+
+//var now = time.Now()
+
+var client *misskey.Client
+var endpoint = os.Getenv("MISSKEY_ENDPOINT")
+var apiKey = os.Getenv("MISSKEY_API_TOKEN")
+
+var (
+ misskeyJobQueueJobsCount = prometheus.NewGaugeVec(
+ prometheus.GaugeOpts{
+ Name: "misskey_jobqueue_jobs",
+ Help: "misskey job queue jobs",
+ },
+ []string{"type", "status"},
+ )
+ misskeyNotesCount = prometheus.NewGauge(
+ prometheus.GaugeOpts{
+ Name: "misskey_notes_count",
+ Help: "Notes Coount",
+ },
+ )
+ misskeyUsersCount = prometheus.NewGauge(
+ prometheus.GaugeOpts{
+ Name: "misskey_users_count",
+ Help: "Users Coount",
+ },
+ )
+ misskeyOriginalNotesCount = prometheus.NewGauge(
+ prometheus.GaugeOpts{
+ Name: "misskey_original_notes_count",
+ Help: "Original Notes Coount",
+ },
+ )
+ misskeyOriginalUsersCount = prometheus.NewGauge(
+ prometheus.GaugeOpts{
+ Name: "misskey_original_users_count",
+ Help: "Original Users Coount",
+ },
+ )
+ misskeyPingResponseTimeRaw = prometheus.NewGauge(
+ prometheus.GaugeOpts{
+ Name: "misskey_ping_response_time_raw",
+ Help: "Response Time for misskey Ping",
+ },
+ )
+ misskeyPingResponseTime = prometheus.NewHistogram(
+ prometheus.HistogramOpts{
+ Name: "misskey_ping_response_time",
+ Help: "Response Time for misskey Ping",
+ Buckets: prometheus.DefBuckets,
+ },
+ )
+ misskeyPingResponseCode = prometheus.NewGauge(
+ prometheus.GaugeOpts{
+ Name: "misskey_ping_response_code",
+ Help: "Response Code for misskey Ping",
+ },
+ )
+ misskeyGlobalTimelineResponseTime = prometheus.NewHistogram(
+ prometheus.HistogramOpts{
+ Name: "misskey_global_timeline_response_time",
+ Help: "Response Time for misskey Global Timeline",
+ })
+ misskeyLocalTimelineResponseTime = prometheus.NewHistogram(
+ prometheus.HistogramOpts{
+ Name: "misskey_local_timeline_response_time",
+ Help: "Response Time for misskey Local Timeline",
+ })
+ misskeyGlobalTimelineLastNotePublished = prometheus.NewGauge(
+ prometheus.GaugeOpts{
+ Name: "misskey_global_timeline_last_note_published",
+ Help: "Last Note Published on Global Timeline",
+ })
+ misskeyLocalTimelineLastNotePublished = prometheus.NewGauge(
+ prometheus.GaugeOpts{
+ Name: "misskey_local_timeline_last_note_published",
+ Help: "Last Note Published on Local Timeline",
+ })
+ misskeyGlobalTimelineResponseTimeRaw = prometheus.NewGauge(
+ prometheus.GaugeOpts{
+ Name: "misskey_global_timeline_response_time_raw",
+ Help: "Response Time for misskey Global Timeline",
+ })
+ misskeyLocalTimelineResponseTimeRaw = prometheus.NewGauge(
+ prometheus.GaugeOpts{
+ Name: "misskey_local_timeline_response_time_raw",
+ Help: "Response Time for misskey Local Timeline",
+ })
+)
+
+type MetricGroup struct {
+ Basename string
+ Help string
+ Type string
+ Value []Metric
+}
+
+type Metric struct {
+ Label map[string]string
+ Value string
+}
+
+func main() {
+ options, err := misskey.NewClientWithOptions(
+ misskey.WithAPIToken(apiKey),
+ misskey.WithBaseURL("https", endpoint, ""),
+ misskey.WithLogLevel(logrus.DebugLevel),
+ )
+ client = options
+ if err != nil {
+ return
+ }
+ stats, err := client.Admin().Queue().Stats()
+ if err != nil {
+ return
+ }
+ fmt.Println(stats)
+
+ prometheus.MustRegister(misskeyJobQueueJobsCount)
+ prometheus.MustRegister(misskeyNotesCount)
+ prometheus.MustRegister(misskeyUsersCount)
+ prometheus.MustRegister(misskeyOriginalNotesCount)
+ prometheus.MustRegister(misskeyOriginalUsersCount)
+ prometheus.MustRegister(misskeyPingResponseTime)
+ prometheus.MustRegister(misskeyPingResponseTimeRaw)
+ prometheus.MustRegister(misskeyPingResponseCode)
+ prometheus.MustRegister(misskeyGlobalTimelineResponseTime)
+ prometheus.MustRegister(misskeyLocalTimelineResponseTime)
+ prometheus.MustRegister(misskeyGlobalTimelineLastNotePublished)
+ prometheus.MustRegister(misskeyLocalTimelineLastNotePublished)
+ prometheus.MustRegister(misskeyGlobalTimelineResponseTimeRaw)
+ prometheus.MustRegister(misskeyLocalTimelineResponseTimeRaw)
+
+ handler := promhttp.Handler()
+
+ server := http.Server{
+ Addr: ":8080",
+ Handler: handler,
+ }
+
+ go func() {
+ go collectJobqueue()
+ go collectPing()
+ go collectStats()
+ go collectTimeline()
+ }()
+
+ server.ListenAndServe()
+}
diff --git a/prometheus.yml b/prometheus.yml
new file mode 100644
index 0000000..4d144b7
--- /dev/null
+++ b/prometheus.yml
@@ -0,0 +1,7 @@
+global:
+ scrape_interval: 1s
+ evaluation_interval: 1s
+scrape_configs:
+ - job_name: 'process_exporter'
+ static_configs:
+ - targets: ['misskey-exporter:8080']
\ No newline at end of file