diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32fd6ea..2155bb7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,13 +25,16 @@ jobs: - run: go version - run: go fmt . - - uses: dominikh/staticcheck-action@v1.3.1 - with: - version: "2023.1.6" - install-go: false + # - uses: dominikh/staticcheck-action@v1 + # with: + # version: "2023.1.7" + # install-go: false + + # - name: Test + # run: make test - - name: Test - run: make test + # - name: Test + # run: go test -run TestDummy - name: Build run: make diff --git a/CHANGES.md b/CHANGES.md index 894874f..e69e291 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,22 @@ - FIX - バグ修正 +## feature/clickhouse + +- [CHANGE] go.mod で go 1.22.4 を要求するようにする + - @voluntas +- [CHANGE] 統計エクスポーターから統計ウェブフックへ変更する + - HTTP/2 対応を削除 + - 設定ファイルに stats_webhook_path を追加 + - @voluntas +- [CHANGE] TimescaleDB から ClickHouse へ変更する + - 設定ファイルに clickhouse_addr を追加 + - 設定ファイルに clickhouse_port を追加 + - 設定ファイルに clickhouse_database を追加 + - 設定ファイルに clickhouse_username を追加 + - 設定ファイルに clickhouse_password を追加 + - @voluntas + ## develop - [UPDATE] sqlc 1.24.0 にする @@ -27,7 +43,7 @@ ## 2023.1.1 - [FIX] HTTP/2 Rapid Reset 対策として Go 1.21.3 以上でリリースバイナリを作成するよう修正する - - https://groups.google.com/g/golang-announce/c/iNNxDTCjZvo + - - @voluntas ## 2023.1.0 diff --git a/VERSION b/VERSION index 51aad1b..a73a851 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2023.1.1 +2024.1.0 diff --git a/cmd/kohaku/main.go b/cmd/kohaku/main.go index 16d6622..70d03f0 100644 --- a/cmd/kohaku/main.go +++ b/cmd/kohaku/main.go @@ -38,15 +38,15 @@ func main() { kohaku.ShowConfig(config) - pool, err := kohaku.NewPool(config.PostgresURI) + conn, err := kohaku.NewConnect(config) if err != nil { // TODO: エラーメッセージを修正する fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err) os.Exit(1) } - defer pool.Close() + defer conn.Close() - server, err := kohaku.NewServer(config, pool) + server, err := kohaku.NewServer(config, conn) if err != nil { log.Fatal(err) } diff --git a/collector_h_test.go b/collector_h_test.go deleted file mode 100644 index 85dd094..0000000 --- a/collector_h_test.go +++ /dev/null @@ -1,838 +0,0 @@ -package kohaku - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "github.com/labstack/echo/v4" - db "github.com/shiguredo/kohaku/gen/sqlc" - "github.com/stretchr/testify/assert" -) - -const ( - channelID = "sora" - connectionID = "QJ253E85SH1C170WQSPYJGFHCR" - clientID = "QJ253E85SH1C170WQSPYJGFHCR" -) - -var ( - timestamp, _ = time.Parse(time.RFC3339Nano, "2021-12-23T02:25:07.471546Z") - multistream = true - simulcast = false - spotlight = false - - collectorSoraConnectionStatsJSON = soraConnectionStats{ - soraStats: soraStats{ - Label: "WebRTC SFU Sora", - NodeName: "sora@127.0.0.1", - Timestamp: timestamp, - Type: "connection.user-agent", - Version: "2021.2.0", - }, - Stats: []json.RawMessage{ - json.RawMessage(`{}`), - }, - ChannelID: "sora", - SessionID: "JTYG1KGGPH2DKF86Y5B0GMWFSM", - ClientID: "QJ253E85SH1C170WQSPYJGFHCR", - ConnectionID: "QJ253E85SH1C170WQSPYJGFHCR", - Role: "sendrecv", - Multistream: &multistream, - Simulcast: &simulcast, - Spotlight: &spotlight, - } -) - -var ( - missingTimestampJSON = `{ - "channel_id": "sora", - "client_id": "QJ253E85SH1C170WQSPYJGFHCR", - "connection_id": "QJ253E85SH1C170WQSPYJGFHCR", - "id": "W8B607ZBG92PD9JTMS19BSTE18", - "label": "WebRTC SFU Sora", - "multistream": true, - "node_name": "sora@127.0.0.1", - "role": "sendrecv", - "session_id": "JTYG1KGGPH2DKF86Y5B0GMWFSM", - "simulcast": false, - "spotlight": false, - "stats": [ - { - "channels": 2, - "id": "RTCCodec_audio_NB1bb0_Inbound_109", - "timestamp": 1640225763760.085, - "type": "codec", - "clockRate": 48000, - "mimeType": "audio/opus", - "payloadType": 109, - "sdpFmtpLine": "minptime=10;useinbandfec=1", - "transportId": "RTCTransport_data_1" - } - ], - "type": "connection.user-agent", - "version": "2021.2.0" - }` -) - -func TestTypeOutboundRTPCollector(t *testing.T) { - // Setup - e := server.echo - - stats := make([]json.RawMessage, 0, 1) - stats = append(stats, json.RawMessage(`{ - "framesEncoded": 892, - "totalPacketSendDelay": 19.477, - "mediaSourceId": "RTCVideoSource_10", - "headerBytesSent": 26760, - "transportId": "RTCTransport_data_1", - "framesPerSecond": 31, - "framesSent": 892, - "id": "RTCOutboundRTPVideoStream_148236668", - "totalEncodeTime": 1.532, - "retransmittedBytesSent": 0, - "keyFramesEncoded": 1, - "frameWidth": 240, - "qualityLimitationDurations": { - "cpu": 0, - "none": 30083, - "other": 0, - "bandwidth": 0 - }, - "packetsSent": 971, - "nackCount": 0, - "encoderImplementation": "libvpx", - "trackId": "RTCMediaStreamTrack_sender_10", - "qualityLimitationReason": "none", - "type": "outbound-rtp", - "firCount": 0, - "codecId": "RTCCodec_video_WvsPAp_Outbound_120", - "totalEncodedBytesTarget": 0, - "kind": "video", - "frameHeight": 160, - "hugeFramesSent": 0, - "pliCount": 0, - "qpSum": 8808, - "bytesSent": 722767, - "timestamp": 1640225763760.085, - "ssrc": 148236668, - "remoteId": "RTCRemoteInboundRtpVideoStream_148236668", - "retransmittedPacketsSent": 0, - "mediaType": "video", - "qualityLimitationResolutionChanges": 0 - }`)) - soraConnectionStatsJSON := collectorSoraConnectionStatsJSON - soraConnectionStatsJSON.Stats = stats - body, err := json.Marshal(soraConnectionStatsJSON) - if err != nil { - panic(err) - } - req := httptest.NewRequest(http.MethodPost, "/collector", bytes.NewReader(body)) - req.Header.Set("content-type", "application/json") - req.Header.Set("x-sora-stats-exporter-type", "connection.user-agent") - req.Proto = "HTTP/2.0" - req.ProtoMajor = 2 - req.ProtoMinor = 0 - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - // Assertions - if assert.NoError(t, server.collector(c)) { - assert.Equal(t, http.StatusNoContent, rec.Code) - - statsType, err := server.query.TestGetUserAgentStatsType(context.Background(), db.TestGetUserAgentStatsTypeParams{ - ChannelID: channelID, - ConnectionID: connectionID, - }) - if err != nil { - panic(err) - } - assert.Equal(t, "outbound-rtp", statsType) - } - server.query.TestDropSoraUserAgentStats(context.Background()) -} - -func TestTypeCodecCollector(t *testing.T) { - // Setup - e := server.echo - - stats := make([]json.RawMessage, 0, 1) - stats = append(stats, json.RawMessage(`{ - "channels": 2, - "id": "RTCCodec_audio_NB1bb0_Inbound_109", - "timestamp": 1640225763760.085, - "type": "codec", - "clockRate": 48000, - "mimeType": "audio/opus", - "payloadType": 109, - "sdpFmtpLine": "minptime=10;useinbandfec=1", - "transportId": "RTCTransport_data_1" - }`)) - soraConnectionStatsJSON := collectorSoraConnectionStatsJSON - soraConnectionStatsJSON.Stats = stats - body, err := json.Marshal(soraConnectionStatsJSON) - if err != nil { - panic(err) - } - req := httptest.NewRequest(http.MethodPost, "/collector", bytes.NewReader(body)) - req.Header.Set("content-type", "application/json") - req.Header.Set("x-sora-stats-exporter-type", "connection.user-agent") - req.Proto = "HTTP/2.0" - req.ProtoMajor = 2 - req.ProtoMinor = 0 - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - // Assertions - if assert.NoError(t, server.collector(c)) { - assert.Equal(t, http.StatusNoContent, rec.Code) - - statsType, err := server.query.TestGetUserAgentStatsType(context.Background(), db.TestGetUserAgentStatsTypeParams{ - ChannelID: channelID, - ConnectionID: connectionID, - }) - if err != nil { - panic(err) - } - assert.Equal(t, "codec", statsType) - } - server.query.TestDropSoraUserAgentStats(context.Background()) -} - -func TestTypeMediaSourceCollector(t *testing.T) { - // Setup - e := server.echo - - stats := make([]json.RawMessage, 0, 2) - stats = append(stats, json.RawMessage(`{ - "id": "RTCAudioSource_9", - "kind": "audio", - "timestamp": 1640225763760.085, - "type": "media-source", - "audioLevel": 0, - "totalAudioEnergy": 0, - "totalSamplesDuration": 30.090000000001904, - "trackIdentifier": "9b36135b-f15f-4779-9aa2-d00609839d2d" - }`)) - stats = append(stats, json.RawMessage(`{ - "height": 160, - "id": "RTCVideoSource_10", - "kind": "video", - "timestamp": 1640225763760.085, - "type": "media-source", - "width": 240, - "frames": 894, - "framesPerSecond": 31, - "trackIdentifier": "425bc57b-5f59-4263-bcc5-579deb8c4d83" - }`)) - soraConnectionStatsJSON := collectorSoraConnectionStatsJSON - soraConnectionStatsJSON.Stats = stats - body, err := json.Marshal(soraConnectionStatsJSON) - if err != nil { - panic(err) - } - req := httptest.NewRequest(http.MethodPost, "/collector", bytes.NewReader(body)) - req.Header.Set("content-type", "application/json") - req.Header.Set("x-sora-stats-exporter-type", "connection.user-agent") - req.Proto = "HTTP/2.0" - req.ProtoMajor = 2 - req.ProtoMinor = 0 - rec := httptest.NewRecorder() - - c := e.NewContext(req, rec) - - // Assertions - if assert.NoError(t, server.collector(c)) { - assert.Equal(t, http.StatusNoContent, rec.Code) - - statsType, err := server.query.TestGetUserAgentStatsType(context.Background(), db.TestGetUserAgentStatsTypeParams{ - ChannelID: channelID, - ConnectionID: connectionID, - }) - if err != nil { - panic(err) - } - assert.Equal(t, "media-source", statsType) - } - server.query.TestDropSoraUserAgentStats(context.Background()) -} - -func TestTypeDataChannelCollector(t *testing.T) { - // Setup - e := server.echo - - stats := make([]json.RawMessage, 0, 4) - stats = append(stats, json.RawMessage(`{ - "id": "RTCDataChannel_17", - "label": "signaling", - "protocol": "", - "state": "open", - "timestamp": 1640225763760.085, - "type": "data-channel", - "bytesReceived": 0, - "bytesSent": 0, - "dataChannelIdentifier": 0, - "messagesReceived": 0, - "messagesSent": 0 - }`)) - stats = append(stats, json.RawMessage(`{ - "id": "RTCDataChannel_18", - "label": "notify", - "protocol": "", - "state": "open", - "timestamp": 1640225763760.085, - "type": "data-channel", - "bytesReceived": 192, - "bytesSent": 0, - "dataChannelIdentifier": 2, - "messagesReceived": 3, - "messagesSent": 0 - }`)) - stats = append(stats, json.RawMessage(`{ - "id": "RTCDataChannel_19", - "label": "push", - "protocol": "", - "state": "open", - "timestamp": 1640225763760.085, - "type": "data-channel", - "bytesReceived": 0, - "bytesSent": 0, - "dataChannelIdentifier": 4, - "messagesReceived": 0, - "messagesSent": 0 - }`)) - stats = append(stats, json.RawMessage(`{ - "id": "RTCDataChannel_20", - "label": "stats", - "protocol": "", - "state": "open", - "timestamp": 1640225763760.085, - "type": "data-channel", - "bytesReceived": 28, - "bytesSent": 0, - "dataChannelIdentifier": 6, - "messagesReceived": 1, - "messagesSent": 0 - }`)) - soraConnectionStatsJSON := collectorSoraConnectionStatsJSON - soraConnectionStatsJSON.Stats = stats - body, err := json.Marshal(soraConnectionStatsJSON) - if err != nil { - panic(err) - } - req := httptest.NewRequest(http.MethodPost, "/collector", bytes.NewReader(body)) - req.Header.Set("content-type", "application/json") - req.Header.Set("x-sora-stats-exporter-type", "connection.user-agent") - req.Proto = "HTTP/2.0" - req.ProtoMajor = 2 - req.ProtoMinor = 0 - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - // Assertions - if assert.NoError(t, server.collector(c)) { - assert.Equal(t, http.StatusNoContent, rec.Code) - - statsType, err := server.query.TestGetUserAgentStatsType(context.Background(), db.TestGetUserAgentStatsTypeParams{ - ChannelID: channelID, - ConnectionID: connectionID, - }) - if err != nil { - panic(err) - } - assert.Equal(t, "data-channel", statsType) - } - server.query.TestDropSoraUserAgentStats(context.Background()) -} - -func TestTypeCandidatePairCollector(t *testing.T) { - // Setup - e := server.echo - - stats := make([]json.RawMessage, 0, 1) - stats = append(stats, json.RawMessage(`{ - "id": "RTCIceCandidatePair_eRplCBvi_JXPaEzOA", - "priority": 179616219446525440, - "state": "succeeded", - "timestamp": 1640225763760.085, - "type": "candidate-pair", - "writable": true, - "availableOutgoingBitrate": 1000000, - "bytesDiscardedOnSend": 0, - "bytesReceived": 5490, - "bytesSent": 833847, - "consentRequestsSent": 15, - "currentRoundTripTime": 0.001, - "localCandidateId": "RTCIceCandidate_eRplCBvi", - "nominated": true, - "packetsDiscardedOnSend": 0, - "packetsReceived": 60, - "packetsSent": 2520, - "remoteCandidateId": "RTCIceCandidate_JXPaEzOA", - "requestsReceived": 14, - "requestsSent": 1, - "responsesReceived": 16, - "responsesSent": 14, - "totalRoundTripTime": 0.032, - "transportId": "RTCTransport_data_1" - }`)) - soraConnectionStatsJSON := collectorSoraConnectionStatsJSON - soraConnectionStatsJSON.Stats = stats - body, err := json.Marshal(soraConnectionStatsJSON) - if err != nil { - panic(err) - } - req := httptest.NewRequest(http.MethodPost, "/collector", bytes.NewReader(body)) - req.Header.Set("content-type", "application/json") - req.Header.Set("x-sora-stats-exporter-type", "connection.user-agent") - req.Proto = "HTTP/2.0" - req.ProtoMajor = 2 - req.ProtoMinor = 0 - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - // Assertions - if assert.NoError(t, server.collector(c)) { - assert.Equal(t, http.StatusNoContent, rec.Code) - - statsType, err := server.query.TestGetUserAgentStatsType(context.Background(), db.TestGetUserAgentStatsTypeParams{ - ChannelID: channelID, - ConnectionID: connectionID, - }) - if err != nil { - panic(err) - } - assert.Equal(t, "candidate-pair", statsType) - } - server.query.TestDropSoraUserAgentStats(context.Background()) -} - -func TestTypeRemoteInboundRTPCollector(t *testing.T) { - // Setup - e := server.echo - - stats := make([]json.RawMessage, 0, 2) - stats = append(stats, json.RawMessage(`{ - "fractionLost": 0, - "id": "RTCRemoteInboundRtpAudioStream_962078423", - "kind": "audio", - "ssrc": 962078423, - "timestamp": 1640225763758.615, - "type": "remote-inbound-rtp", - "codecId": "RTCCodec_audio_NB1bb0_Outbound_109", - "jitter": 0.0021041666666666665, - "localId": "RTCOutboundRTPAudioStream_962078423", - "packetsLost": 0, - "roundTripTime": 0.002, - "roundTripTimeMeasurements": 6, - "totalRoundTripTime": 0.009, - "transportId": "RTCTransport_data_1" - }`)) - stats = append(stats, json.RawMessage(`{ - "fractionLost": 0, - "id": "RTCRemoteInboundRtpVideoStream_148236668", - "kind": "video", - "ssrc": 148236668, - "timestamp": 1640225763393.525, - "type": "remote-inbound-rtp", - "codecId": "RTCCodec_video_WvsPAp_Outbound_120", - "jitter": 0.0017111111111111112, - "localId": "RTCOutboundRTPVideoStream_148236668", - "packetsLost": 0, - "roundTripTime": 0.003, - "roundTripTimeMeasurements": 37, - "totalRoundTripTime": 0.059, - "transportId": "RTCTransport_data_1" - }`)) - soraConnectionStatsJSON := collectorSoraConnectionStatsJSON - soraConnectionStatsJSON.Stats = stats - body, err := json.Marshal(soraConnectionStatsJSON) - if err != nil { - panic(err) - } - req := httptest.NewRequest(http.MethodPost, "/collector", bytes.NewReader(body)) - req.Header.Set("content-type", "application/json") - req.Header.Set("x-sora-stats-exporter-type", "connection.user-agent") - req.Proto = "HTTP/2.0" - req.ProtoMajor = 2 - req.ProtoMinor = 0 - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - // Assertions - if assert.NoError(t, server.collector(c)) { - assert.Equal(t, http.StatusNoContent, rec.Code) - - statsType, err := server.query.TestGetUserAgentStatsType(context.Background(), db.TestGetUserAgentStatsTypeParams{ - ChannelID: channelID, - ConnectionID: connectionID, - }) - if err != nil { - panic(err) - } - assert.Equal(t, "remote-inbound-rtp", statsType) - } - server.query.TestDropSoraUserAgentStats(context.Background()) -} - -func TestTypeTransportCollector(t *testing.T) { - // Setup - e := server.echo - - stats := make([]json.RawMessage, 0, 1) - stats = append(stats, json.RawMessage(`{ - "id": "RTCTransport_data_1", - "timestamp": 1640225763760.085, - "type": "transport", - "bytesReceived": 5490, - "bytesSent": 833847, - "dtlsCipher": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "dtlsState": "connected", - "localCertificateId": "RTCCertificate_66:F6:14:8E:B3:3E:C1:44:D0:DB:3C:2B:1C:35:7E:F4:4B:3A:6C:87:AD:E2:09:06:7C:EB:5B:DD:62:6F:36:40", - "packetsReceived": 60, - "packetsSent": 2520, - "remoteCertificateId": "RTCCertificate_A9:4A:03:B1:A9:66:46:EC:AD:03:73:D8:1E:99:46:06:5C:56:E9:00:AC:A5:F9:7C:50:8C:28:16:2A:E5:BF:07", - "selectedCandidatePairChanges": 1, - "selectedCandidatePairId": "RTCIceCandidatePair_eRplCBvi_JXPaEzOA", - "srtpCipher": "AEAD_AES_128_GCM", - "tlsVersion": "FEFD" - }`)) - soraConnectionStatsJSON := collectorSoraConnectionStatsJSON - soraConnectionStatsJSON.Stats = stats - body, err := json.Marshal(soraConnectionStatsJSON) - if err != nil { - panic(err) - } - req := httptest.NewRequest(http.MethodPost, "/collector", bytes.NewReader(body)) - req.Header.Set("content-type", "application/json") - req.Header.Set("x-sora-stats-exporter-type", "connection.user-agent") - req.Proto = "HTTP/2.0" - req.ProtoMajor = 2 - req.ProtoMinor = 0 - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - // Assertions - if assert.NoError(t, server.collector(c)) { - assert.Equal(t, http.StatusNoContent, rec.Code) - - statsType, err := server.query.TestGetUserAgentStatsType(context.Background(), db.TestGetUserAgentStatsTypeParams{ - ChannelID: channelID, - ConnectionID: connectionID, - }) - if err != nil { - panic(err) - } - assert.Equal(t, "transport", statsType) - } - - server.query.TestDropSoraUserAgentStats(context.Background()) -} - -func TestInvalidConnectionIDLength(t *testing.T) { - // Setup - e := server.echo - - stats := make([]json.RawMessage, 0, 1) - stats = append(stats, json.RawMessage(`{ - "channels": 2, - "id": "RTCCodec_audio_NB1bb0_Inbound_109", - "timestamp": 1640225763760.085, - "type": "codec", - "clockRate": 48000, - "mimeType": "audio/opus", - "payloadType": 109, - "sdpFmtpLine": "minptime=10;useinbandfec=1", - "transportId": "RTCTransport_data_1" - }`)) - soraConnectionStatsJSON := collectorSoraConnectionStatsJSON - soraConnectionStatsJSON.ConnectionID = "QJ253E85SH1C170WQSPYJGFHCR=" - soraConnectionStatsJSON.Stats = stats - body, err := json.Marshal(soraConnectionStatsJSON) - if err != nil { - panic(err) - } - req := httptest.NewRequest(http.MethodPost, "/collector", bytes.NewReader(body)) - req.Header.Set("content-type", "application/json") - req.Header.Set("x-sora-stats-exporter-type", "connection.user-agent") - req.Proto = "HTTP/2.0" - req.ProtoMajor = 2 - req.ProtoMinor = 0 - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - httpErr := server.collector(c) - - if assert.Error(t, httpErr) { - assert.Equal(t, http.StatusBadRequest, httpErr.(*echo.HTTPError).Code) - assert.NotEmpty(t, httpErr.(*echo.HTTPError).Message) - assert.Equal(t, `code=400, message=Key: 'soraConnectionStats.ConnectionID' Error:Field validation for 'ConnectionID' failed on the 'len' tag`, httpErr.(*echo.HTTPError).Message) - } - - server.query.TestDropSoraUserAgentStats(context.Background()) -} - -func TestUnexpectedType(t *testing.T) { - // Setup - e := server.echo - - stats := make([]json.RawMessage, 0, 1) - stats = append(stats, json.RawMessage(`{ - "channels": 2, - "id": "RTCCodec_audio_NB1bb0_Inbound_109", - "timestamp": 1640225763760.085, - "type": "codec", - "clockRate": 48000, - "mimeType": "audio/opus", - "payloadType": 109, - "sdpFmtpLine": "minptime=10;useinbandfec=1", - "transportId": "RTCTransport_data_1" - }`)) - soraConnectionStatsJSON := collectorSoraConnectionStatsJSON - soraConnectionStatsJSON.Type = "connection.unexpected_type" - soraConnectionStatsJSON.Stats = stats - body, err := json.Marshal(soraConnectionStatsJSON) - if err != nil { - panic(err) - } - req := httptest.NewRequest(http.MethodPost, "/collector", bytes.NewReader(body)) - req.Header.Set("content-type", "application/json") - req.Header.Set("x-sora-stats-exporter-type", "connection.unexpected_type") - req.Proto = "HTTP/2.0" - req.ProtoMajor = 2 - req.ProtoMinor = 0 - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - // Assertions - httpErr := server.collector(c) - - if assert.Error(t, httpErr) { - assert.Equal(t, http.StatusBadRequest, httpErr.(*echo.HTTPError).Code) - assert.NotEmpty(t, httpErr.(*echo.HTTPError).Message) - // TODO: エラーメッセージの内容の確認 - assert.Equal(t, `Bad Request`, httpErr.(*echo.HTTPError).Message) - } - - server.query.TestDropSoraUserAgentStats(context.Background()) -} - -func TestMissingTimestamp(t *testing.T) { - // Setup - e := server.echo - - req := httptest.NewRequest(http.MethodPost, "/collector", strings.NewReader(missingTimestampJSON)) - req.Header.Set("content-type", "application/json") - req.Header.Set("x-sora-stats-exporter-type", "connection.user-agent") - req.Proto = "HTTP/2.0" - req.ProtoMajor = 2 - req.ProtoMinor = 0 - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - // Assertions - httpErr := server.collector(c) - if assert.Error(t, httpErr) { - assert.Equal(t, http.StatusBadRequest, httpErr.(*echo.HTTPError).Code) - assert.NotEmpty(t, httpErr.(*echo.HTTPError).Message) - assert.Equal(t, `code=400, message=Key: 'soraConnectionStats.soraStats.Timestamp' Error:Field validation for 'Timestamp' failed on the 'required' tag`, httpErr.(*echo.HTTPError).Message) - } -} - -func TestInvalidChannelIDLength(t *testing.T) { - // Setup - e := server.echo - - stats := make([]json.RawMessage, 0, 1) - stats = append(stats, json.RawMessage(`{ - "channels": 2, - "id": "RTCCodec_audio_NB1bb0_Inbound_109", - "timestamp": 1640225763760.085, - "type": "codec", - "clockRate": 48000, - "mimeType": "audio/opus", - "payloadType": 109, - "sdpFmtpLine": "minptime=10;useinbandfec=1", - "transportId": "RTCTransport_data_1" - }`)) - soraConnectionStatsJSON := collectorSoraConnectionStatsJSON - soraConnectionStatsJSON.ChannelID = "2QB23E50YD6FKEFG9GW2TX86RC2QB23E50YD6FKEFG9GW2TX86RC2QB23E50YD6FKEFG9GW2TX86RC2QB23E50YD6FKEFG9GW2TX86RC2QB23E50YD6FKEFG9GW2TX86RC2QB23E50YD6FKEFG9GW2TX86RC2QB23E50YD6FKEFG9GW2TX86RC2QB23E50YD6FKEFG9GW2TX86RC2QB23E50YD6FKEFG9GW2TX86RC2QB23E50YD6FKEFG9GW2TX" - soraConnectionStatsJSON.Stats = stats - body, err := json.Marshal(soraConnectionStatsJSON) - if err != nil { - panic(err) - } - req := httptest.NewRequest(http.MethodPost, "/collector", bytes.NewReader(body)) - req.Header.Set("content-type", "application/json") - req.Header.Set("x-sora-stats-exporter-type", "connection.user-agent") - req.Proto = "HTTP/2.0" - req.ProtoMajor = 2 - req.ProtoMinor = 0 - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - // Assertions - httpErr := server.collector(c) - if assert.Error(t, httpErr) { - assert.Equal(t, http.StatusBadRequest, httpErr.(*echo.HTTPError).Code) - assert.NotEmpty(t, httpErr.(*echo.HTTPError).Message) - assert.Equal(t, `code=400, message=Key: 'soraConnectionStats.ChannelID' Error:Field validation for 'ChannelID' failed on the 'maxb' tag`, httpErr.(*echo.HTTPError).Message) - } -} - -func TestMissingMultistream(t *testing.T) { - // Setup - e := server.echo - - stats := make([]json.RawMessage, 0, 1) - stats = append(stats, json.RawMessage(`{ - "channels": 2, - "id": "RTCCodec_audio_NB1bb0_Inbound_109", - "timestamp": 1640225763760.085, - "type": "codec", - "clockRate": 48000, - "mimeType": "audio/opus", - "payloadType": 109, - "sdpFmtpLine": "minptime=10;useinbandfec=1", - "transportId": "RTCTransport_data_1" - }`)) - soraConnectionStatsJSON := collectorSoraConnectionStatsJSON - soraConnectionStatsJSON.Multistream = nil - soraConnectionStatsJSON.Stats = stats - body, err := json.Marshal(soraConnectionStatsJSON) - if err != nil { - panic(err) - } - req := httptest.NewRequest(http.MethodPost, "/collector", bytes.NewReader(body)) - req.Header.Set("content-type", "application/json") - req.Header.Set("x-sora-stats-exporter-type", "connection.user-agent") - req.Proto = "HTTP/2.0" - req.ProtoMajor = 2 - req.ProtoMinor = 0 - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - // Assertions - httpErr := server.collector(c) - if assert.Error(t, httpErr) { - assert.Equal(t, http.StatusBadRequest, httpErr.(*echo.HTTPError).Code) - assert.NotEmpty(t, httpErr.(*echo.HTTPError).Message) - assert.Equal(t, `code=400, message=Key: 'soraConnectionStats.Multistream' Error:Field validation for 'Multistream' failed on the 'required' tag`, httpErr.(*echo.HTTPError).Message) - } -} - -// TODO: type のチェックを kohaku ではしていない -// func TestUnexpectedStatsType(t *testing.T) { -// // Setup -// e := server.echo -// -// stats := make([]json.RawMessage, 0, 1) -// stats = append(stats, json.RawMessage(`{ -// "channels": 2, -// "id": "RTCCodec_audio_NB1bb0_Inbound_109", -// "timestamp": 1640225763760.085, -// "type": "unexpected_type", -// "clockRate": 48000, -// "mimeType": "audio/opus", -// "payloadType": 109, -// "sdpFmtpLine": "minptime=10;useinbandfec=1", -// "transportId": "RTCTransport_data_1" -// }`)) -// soraConnectionStatsJSON := collectorSoraConnectionStatsJSON -// soraConnectionStatsJSON.Stats = stats -// body, err := json.Marshal(soraConnectionStatsJSON) -// if err != nil { -// panic(err) -// } -// req := httptest.NewRequest(http.MethodPost, "/collector", bytes.NewReader(body)) -// req.Header.Set("content-type", "application/json") -// req.Header.Set("x-sora-stats-exporter-type", "connection.user-agent") -// req.Proto = "HTTP/2.0" -// req.ProtoMajor = 2 -// req.ProtoMinor = 0 -// rec := httptest.NewRecorder() -// c := e.NewContext(req, rec) -// -// // Assertions -// httpErr := server.collector(c) -// if assert.Error(t, httpErr) { -// assert.Equal(t, http.StatusBadRequest, httpErr.(*echo.HTTPError).Code) -// assert.NotEmpty(t, httpErr.(*echo.HTTPError).Message) -// assert.Equal(t, `unexpected rtcStats.Type: unexpected_type`, httpErr.(*echo.HTTPError).Message) -// } -// } - -func TestDuplicate(t *testing.T) { - // Setup - e := server.echo - - stats := make([]json.RawMessage, 0, 1) - stats = append(stats, json.RawMessage(`{ - "channels": 2, - "id": "RTCCodec_audio_NB1bb0_Inbound_109", - "timestamp": 1640225763761.085, - "type": "codec", - "clockRate": 48000, - "mimeType": "audio/opus", - "payloadType": 109, - "sdpFmtpLine": "minptime=10;useinbandfec=1", - "transportId": "RTCTransport_data_1" - }`)) - soraConnectionStatsJSON := collectorSoraConnectionStatsJSON - soraConnectionStatsJSON.Stats = stats - body, err := json.Marshal(soraConnectionStatsJSON) - if err != nil { - panic(err) - } - req := httptest.NewRequest(http.MethodPost, "/collector", bytes.NewReader(body)) - req.Header.Set("content-type", "application/json") - req.Header.Set("x-sora-stats-exporter-type", "connection.user-agent") - req.Proto = "HTTP/2.0" - req.ProtoMajor = 2 - req.ProtoMinor = 0 - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - // Assertions - if assert.NoError(t, server.collector(c)) { - assert.Equal(t, http.StatusNoContent, rec.Code) - - statsType, err := server.query.TestGetUserAgentStatsType(context.Background(), - db.TestGetUserAgentStatsTypeParams{ - ChannelID: channelID, - ConnectionID: connectionID, - }) - assert.Nil(t, err) - assert.Equal(t, "codec", statsType) - } - - req = httptest.NewRequest(http.MethodPost, "/collector", bytes.NewReader(body)) - req.Header.Set("content-type", "application/json") - req.Header.Set("x-sora-stats-exporter-type", "connection.user-agent") - req.Proto = "HTTP/2.0" - req.ProtoMajor = 2 - req.ProtoMinor = 0 - rec = httptest.NewRecorder() - c = e.NewContext(req, rec) - - // Assertions - if assert.NoError(t, server.collector(c)) { - assert.Equal(t, http.StatusNoContent, rec.Code) - - count, err := server.query.TestGetUserAgentStatsTypeCount(context.Background(), - db.TestGetUserAgentStatsTypeCountParams{ - RtcTypeStats: "codec", - ChannelID: channelID, - ConnectionID: connectionID, - }) - assert.Nil(t, err) - assert.Equal(t, int64(1), count) - } - - server.query.TestDropSoraUserAgentStats(context.Background()) -} diff --git a/compose.yaml b/compose.yaml index ca23ad7..c385e53 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,33 +1,30 @@ -version: "3.9" name: "kohaku-dev" services: - timescaledb: - image: timescale/timescaledb:latest-pg15 - ports: - - "5432:5432" + clickhouse: + image: clickhouse/clickhouse-server:latest environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=password - - POSTGRES_DB=kohaku - healthcheck: - test: pg_isready -U postgres - interval: 5s - command: -c 'config_file=/etc/postgresql/postgresql.conf' + - CLICKHOUSE_DB=default + - CLICKHOUSE_USER=default + - CLICKHOUSE_PASSWORD=default + - CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 volumes: - - ./db/postgresql.conf:/etc/postgresql/postgresql.conf - # https://github.com/timescale/timescaledb-docker/tree/main/docker-entrypoint-initdb.d - # timescaledb 側で 001 と 002 があるのでその後に実行するために 100 からスタート - - ./db/schema.sql:/docker-entrypoint-initdb.d/100_schema.sql - - grafana: - image: grafana/grafana:latest + # - ./clickhouse/config.xml:/etc/clickhouse-server/config.d/config.xml + # - ./clickhouse/users.xml:/etc/clickhous-server/users.d/users.xml + - ./db/schema.sql:/docker-entrypoint-initdb.d/schema.sql ports: - - "3333:3000" - volumes: - - ./.grafana:/var/lib/grafana - - ./grafana/datasources:/etc/grafana/provisioning/datasources - - ./grafana/dashboards/kohaku.yml:/etc/grafana/provisioning/dashboards/kohaku.yml - - ./grafana/dashboards/kohaku:/var/lib/grafana/dashboards/kohaku - depends_on: - - timescaledb + - "8123:8123" + - "9000:9000" + - "9440:9440" + + # grafana: + # image: grafana/grafana:latest + # ports: + # - "3333:3000" + # volumes: + # - ./.grafana:/var/lib/grafana + # - ./grafana/datasources:/etc/grafana/provisioning/datasources + # - ./grafana/dashboards/kohaku.yml:/etc/grafana/provisioning/dashboards/kohaku.yml + # - ./grafana/dashboards/kohaku:/var/lib/grafana/dashboards/kohaku + # depends_on: + # - clickhouse diff --git a/config.go b/config.go index b58e6a7..689cb8a 100644 --- a/config.go +++ b/config.go @@ -14,6 +14,8 @@ import ( var Version string const ( + DefaultStatsWebhookPath = "/stats" + DefaultLogDir = "." DefaultLogName = "kohaku.jsonl" @@ -40,6 +42,17 @@ type Config struct { // Days LogRotateMaxAge int `ini:"log_rotate_max_age"` + StatsWebhookPath string `ini:"stats_webhook_path"` + + ClickHouseAddr string `ini:"clickhouse_addr"` + ClickHousePort int `ini:"clickhouse_port"` + + ClickHouseDatabase string `ini:"clickhouse_database"` + ClickHouseUsername string `ini:"clickhouse_username"` + ClickHousePassword string `ini:"clickhouse_password"` + + ClickHouseDebug bool `ini:"click_house_debug"` + HTTPS bool `ini:"https"` ListenAddr string `ini:"listen_addr"` ListenPort int `ini:"listen_port"` @@ -50,16 +63,9 @@ type Config struct { ExporterListenAddr string `ini:"exporter_listen_addr"` ExporterListenPort int `ini:"exporter_listen_port"` - PostgresURI string `ini:"postgres_uri"` - PostgresCACertFile string `ini:"postgres_ca_cert_file"` - TLSFullchainFile string `ini:"tls_fullchain_file"` TLSPrivkeyFile string `ini:"tls_privkey_file"` TLSVerifyCacertPath string `ini:"tls_verify_cacert_path"` - - HTTP2MaxConcurrentStreams uint32 `ini:"http2_max_concurrent_streams"` - HTTP2MaxReadFrameSize uint32 `ini:"http2_max_read_frame_size"` - HTTP2IdleTimeout uint32 `ini:"http2_idle_timeout"` } func NewConfig(configFilePath string) (*Config, error) { @@ -84,6 +90,10 @@ func NewConfig(configFilePath string) (*Config, error) { } func setDefaultsConfig(config *Config) { + if config.StatsWebhookPath == "" { + config.StatsWebhookPath = DefaultStatsWebhookPath + } + if config.LogDir == "" { config.LogDir = DefaultLogDir } @@ -144,6 +154,8 @@ func ShowConfig(config *Config) { zlog.Info().Bool("debug", config.Debug).Msg("CONF") + zlog.Info().Str("stats_webhook_path", config.StatsWebhookPath).Msg("CONF") + zlog.Info().Str("log_dir", config.LogDir).Msg("CONF") zlog.Info().Str("log_name", config.LogName).Msg("CONF") zlog.Info().Bool("log_stdout", config.LogStdout).Msg("CONF") @@ -159,4 +171,10 @@ func ShowConfig(config *Config) { zlog.Info().Bool("exporter_https", config.ExporterHTTPS).Msg("CONF") zlog.Info().Str("exporter_listen_addr", config.ExporterListenAddr).Msg("CONF") zlog.Info().Int("exporter_listen_port", config.ExporterListenPort).Msg("CONF") + + zlog.Info().Str("clickhouse_addr", config.ClickHouseAddr).Msg("CONF") + zlog.Info().Int("clickhouse_port", config.ClickHousePort).Msg("CONF") + zlog.Info().Str("clickhouse_database", config.ClickHouseDatabase).Msg("CONF") + zlog.Info().Str("clickhouse_username", config.ClickHouseUsername).Msg("CONF") + zlog.Info().Str("clickhouse_password", config.ClickHousePassword).Msg("CONF") } diff --git a/config_example.ini b/config_example.ini index 77a21e5..622ece8 100644 --- a/config_example.ini +++ b/config_example.ini @@ -1,5 +1,8 @@ debug = true +# Sora の stats_webhook_url 向けのパスです +stats_webhook_path = /stats + # Kohaku の HTTP リクエストで HTTPS を利用するかどうか https = false # Kohaku の HTTP リクエストの待ち受けアドレスとポートです @@ -34,11 +37,16 @@ log_stdout = true # 古いログファイルを保持する最大日数です #log_rotate_max_age = 30 -# kohaku が利用する TimescaleDB の URI です -postgres_uri = postgres://postgres:password@127.0.0.1:5432/kohaku -# kohaku が利用する TimescaleDB の CA 証明書ファイルです -# postgres_ca_cert_file = cert/postgres/ca.pem +# kohaku が利用する ClickHouse の IP アドレスを指定してください +clickhouse_addr = 127.0.0.1 + +# kohaku が利用する ClickHouse のポート番号を指定してください +clickhouse_port = 9000 + +clickhouse_database = default +clickhouse_username = default +clickhouse_password = default +# kohaku が利用する ClickHouse の CA 証明書ファイルです +# clickhouse_ca_cert_file = cert/postgres/ca.pem -http2_max_concurrent_streams = 250 -http2_max_read_frame_size = 1048576 -http2_idle_timeout = 60 +# clickhouse_debug = true diff --git a/db/postgresql.conf b/db/postgresql.conf deleted file mode 100644 index 9d9ca7b..0000000 --- a/db/postgresql.conf +++ /dev/null @@ -1,782 +0,0 @@ -# ----------------------------- -# PostgreSQL configuration file -# ----------------------------- -# -# This file consists of lines of the form: -# -# name = value -# -# (The "=" is optional.) Whitespace may be used. Comments are introduced with -# "#" anywhere on a line. The complete list of parameter names and allowed -# values can be found in the PostgreSQL documentation. -# -# The commented-out settings shown in this file represent the default values. -# Re-commenting a setting is NOT sufficient to revert it to the default value; -# you need to reload the server. -# -# This file is read on server startup and when the server receives a SIGHUP -# signal. If you edit the file on a running system, you have to SIGHUP the -# server for the changes to take effect, run "pg_ctl reload", or execute -# "SELECT pg_reload_conf()". Some parameters, which are marked below, -# require a server shutdown and restart to take effect. -# -# Any parameter can also be given as a command-line option to the server, e.g., -# "postgres -c log_connections=on". Some parameters can be changed at run time -# with the "SET" SQL command. -# -# Memory units: B = bytes Time units: us = microseconds -# kB = kilobytes ms = milliseconds -# MB = megabytes s = seconds -# GB = gigabytes min = minutes -# TB = terabytes h = hours -# d = days - -#------------------------------------------------------------------------------ -# FILE LOCATIONS -#------------------------------------------------------------------------------ - -# The default values of these variables are driven from the -D command-line -# option or PGDATA environment variable, represented here as ConfigDir. - -#data_directory = 'ConfigDir' # use data in another directory - # (change requires restart) -#hba_file = 'ConfigDir/pg_hba.conf' # host-based authentication file - # (change requires restart) -#ident_file = 'ConfigDir/pg_ident.conf' # ident configuration file - # (change requires restart) - -# If external_pid_file is not explicitly set, no extra PID file is written. -#external_pid_file = '' # write an extra PID file - # (change requires restart) - -#------------------------------------------------------------------------------ -# CONNECTIONS AND AUTHENTICATION -#------------------------------------------------------------------------------ - -# - Connection Settings - - -listen_addresses = '*' - # comma-separated list of addresses; - # defaults to 'localhost'; use '*' for all - # (change requires restart) -#port = 5432 # (change requires restart) -max_connections = 100 # (change requires restart) -#superuser_reserved_connections = 3 # (change requires restart) -#unix_socket_directories = '/var/run/postgresql' # comma-separated list of directories - # (change requires restart) -#unix_socket_group = '' # (change requires restart) -#unix_socket_permissions = 0777 # begin with 0 to use octal notation - # (change requires restart) -#bonjour = off # advertise server via Bonjour - # (change requires restart) -#bonjour_name = '' # defaults to the computer name - # (change requires restart) - -# - TCP settings - -# see "man tcp" for details - -#tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds; - # 0 selects the system default -#tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds; - # 0 selects the system default -#tcp_keepalives_count = 0 # TCP_KEEPCNT; - # 0 selects the system default -#tcp_user_timeout = 0 # TCP_USER_TIMEOUT, in milliseconds; - # 0 selects the system default - -#client_connection_check_interval = 0 # time between checks for client - # disconnection while running queries; - # 0 for never - -# - Authentication - - -#authentication_timeout = 1min # 1s-600s -#password_encryption = scram-sha-256 # scram-sha-256 or md5 -#db_user_namespace = off - -# GSSAPI using Kerberos -#krb_server_keyfile = 'FILE:${sysconfdir}/krb5.keytab' -#krb_caseins_users = off - -# - SSL - - -#ssl = off -#ssl_ca_file = '' -#ssl_cert_file = 'server.crt' -#ssl_crl_file = '' -#ssl_crl_dir = '' -#ssl_key_file = 'server.key' -#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers -#ssl_prefer_server_ciphers = on -#ssl_ecdh_curve = 'prime256v1' -#ssl_min_protocol_version = 'TLSv1.2' -#ssl_max_protocol_version = '' -#ssl_dh_params_file = '' -#ssl_passphrase_command = '' -#ssl_passphrase_command_supports_reload = off - -#------------------------------------------------------------------------------ -# RESOURCE USAGE (except WAL) -#------------------------------------------------------------------------------ - -# - Memory - - -shared_buffers = 1987MB # min 128kB - # (change requires restart) -#huge_pages = try # on, off, or try - # (change requires restart) -#huge_page_size = 0 # zero for system default - # (change requires restart) -#temp_buffers = 8MB # min 800kB -#max_prepared_transactions = 0 # zero disables the feature - # (change requires restart) -# Caution: it is not advisable to set max_prepared_transactions nonzero unless -# you actively intend to use prepared transactions. -work_mem = 3392kB # min 64kB -#hash_mem_multiplier = 1.0 # 1-1000.0 multiplier on hash table work_mem -maintenance_work_mem = 1017749kB # min 1MB -#autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem -#logical_decoding_work_mem = 64MB # min 64kB -#max_stack_depth = 2MB # min 100kB -#shared_memory_type = mmap # the default is the first option - # supported by the operating system: - # mmap - # sysv - # windows - # (change requires restart) -dynamic_shared_memory_type = posix # the default is the first option - # supported by the operating system: - # posix - # sysv - # windows - # mmap - # (change requires restart) -#min_dynamic_shared_memory = 0MB # (change requires restart) - -# - Disk - - -#temp_file_limit = -1 # limits per-process temp file space - # in kilobytes, or -1 for no limit - -# - Kernel Resources - - -#max_files_per_process = 1000 # min 64 - # (change requires restart) - -# - Cost-Based Vacuum Delay - - -#vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables) -#vacuum_cost_page_hit = 1 # 0-10000 credits -#vacuum_cost_page_miss = 2 # 0-10000 credits -#vacuum_cost_page_dirty = 20 # 0-10000 credits -#vacuum_cost_limit = 200 # 1-10000 credits - -# - Background Writer - - -#bgwriter_delay = 200ms # 10-10000ms between rounds -#bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables -#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round -#bgwriter_flush_after = 512kB # measured in pages, 0 disables - -# - Asynchronous Behavior - - -#backend_flush_after = 0 # measured in pages, 0 disables -effective_io_concurrency = 256 # 1-1000; 0 disables prefetching -#maintenance_io_concurrency = 10 # 1-1000; 0 disables prefetching -max_worker_processes = 24 # (change requires restart) -max_parallel_workers_per_gather = 8 # taken from max_parallel_workers -#max_parallel_maintenance_workers = 2 # taken from max_parallel_workers -max_parallel_workers = 16 # maximum number of max_worker_processes that - # can be used in parallel operations -#parallel_leader_participation = on -#old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate - # (change requires restart) - -#------------------------------------------------------------------------------ -# WRITE-AHEAD LOG -#------------------------------------------------------------------------------ - -# - Settings - - -#wal_level = replica # minimal, replica, or logical - # (change requires restart) -#fsync = on # flush data to disk for crash safety - # (turning this off can cause - # unrecoverable data corruption) -#synchronous_commit = on # synchronization level; - # off, local, remote_write, remote_apply, or on -#wal_sync_method = fsync # the default is the first option - # supported by the operating system: - # open_datasync - # fdatasync (default on Linux and FreeBSD) - # fsync - # fsync_writethrough - # open_sync -#full_page_writes = on # recover from partial page writes -#wal_log_hints = off # also do full page writes of non-critical updates - # (change requires restart) -#wal_compression = off # enable compression of full-page writes -#wal_init_zero = on # zero-fill new WAL files -#wal_recycle = on # recycle WAL files -wal_buffers = 16MB # min 32kB, -1 sets based on shared_buffers - # (change requires restart) -#wal_writer_delay = 200ms # 1-10000 milliseconds -#wal_writer_flush_after = 1MB # measured in pages, 0 disables -#wal_skip_threshold = 2MB - -#commit_delay = 0 # range 0-100000, in microseconds -#commit_siblings = 5 # range 1-1000 - -# - Checkpoints - - -#checkpoint_timeout = 5min # range 30s-1d -checkpoint_completion_target = 0.9 # checkpoint target duration, 0.0 - 1.0 -#checkpoint_flush_after = 256kB # measured in pages, 0 disables -#checkpoint_warning = 30s # 0 disables -max_wal_size = 1GB -min_wal_size = 512MB - -# - Archiving - - -#archive_mode = off # enables archiving; off, on, or always - # (change requires restart) -#archive_command = '' # command to use to archive a logfile segment - # placeholders: %p = path of file to archive - # %f = file name only - # e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' -#archive_timeout = 0 # force a logfile segment switch after this - # number of seconds; 0 disables - -# - Archive Recovery - - -# These are only used in recovery mode. - -#restore_command = '' # command to use to restore an archived logfile segment - # placeholders: %p = path of file to restore - # %f = file name only - # e.g. 'cp /mnt/server/archivedir/%f %p' -#archive_cleanup_command = '' # command to execute at every restartpoint -#recovery_end_command = '' # command to execute at completion of recovery - -# - Recovery Target - - -# Set these only when performing a targeted recovery. - -#recovery_target = '' # 'immediate' to end recovery as soon as a -# consistent state is reached - # (change requires restart) -#recovery_target_name = '' # the named restore point to which recovery will proceed - # (change requires restart) -#recovery_target_time = '' # the time stamp up to which recovery will proceed - # (change requires restart) -#recovery_target_xid = '' # the transaction ID up to which recovery will proceed - # (change requires restart) -#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed - # (change requires restart) -#recovery_target_inclusive = on # Specifies whether to stop: - # just after the specified recovery target (on) - # just before the recovery target (off) - # (change requires restart) -#recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID - # (change requires restart) -#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown' - # (change requires restart) - -#------------------------------------------------------------------------------ -# REPLICATION -#------------------------------------------------------------------------------ - -# - Sending Servers - - -# Set these on the primary and on any standby that will send replication data. - -#max_wal_senders = 10 # max number of walsender processes - # (change requires restart) -#max_replication_slots = 10 # max number of replication slots - # (change requires restart) -#wal_keep_size = 0 # in megabytes; 0 disables -#max_slot_wal_keep_size = -1 # in megabytes; -1 disables -#wal_sender_timeout = 60s # in milliseconds; 0 disables -#track_commit_timestamp = off # collect timestamp of transaction commit - # (change requires restart) - -# - Primary Server - - -# These settings are ignored on a standby server. - -#synchronous_standby_names = '' # standby servers that provide sync rep - # method to choose sync standbys, number of sync standbys, - # and comma-separated list of application_name - # from standby(s); '*' = all -#vacuum_defer_cleanup_age = 0 # number of xacts by which cleanup is delayed - -# - Standby Servers - - -# These settings are ignored on a primary server. - -#primary_conninfo = '' # connection string to sending server -#primary_slot_name = '' # replication slot on sending server -#promote_trigger_file = '' # file name whose presence ends recovery -#hot_standby = on # "off" disallows queries during recovery - # (change requires restart) -#max_standby_archive_delay = 30s # max delay before canceling queries - # when reading WAL from archive; - # -1 allows indefinite delay -#max_standby_streaming_delay = 30s # max delay before canceling queries - # when reading streaming WAL; - # -1 allows indefinite delay -#wal_receiver_create_temp_slot = off # create temp slot if primary_slot_name - # is not set -#wal_receiver_status_interval = 10s # send replies at least this often - # 0 disables -#hot_standby_feedback = off # send info from standby to prevent - # query conflicts -#wal_receiver_timeout = 60s # time that receiver waits for - # communication from primary - # in milliseconds; 0 disables -#wal_retrieve_retry_interval = 5s # time to wait before retrying to - # retrieve WAL after a failed attempt -#recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery - -# - Subscribers - - -# These settings are ignored on a publisher. - -#max_logical_replication_workers = 4 # taken from max_worker_processes - # (change requires restart) -#max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers - -#------------------------------------------------------------------------------ -# QUERY TUNING -#------------------------------------------------------------------------------ - -# - Planner Method Configuration - - -#enable_async_append = on -#enable_bitmapscan = on -#enable_gathermerge = on -#enable_hashagg = on -#enable_hashjoin = on -#enable_incremental_sort = on -#enable_indexscan = on -#enable_indexonlyscan = on -#enable_material = on -#enable_memoize = on -#enable_mergejoin = on -#enable_nestloop = on -#enable_parallel_append = on -#enable_parallel_hash = on -#enable_partition_pruning = on -#enable_partitionwise_join = off -#enable_partitionwise_aggregate = off -#enable_seqscan = on -#enable_sort = on -#enable_tidscan = on - -# - Planner Cost Constants - - -#seq_page_cost = 1.0 # measured on an arbitrary scale -random_page_cost = 1.1 # same scale as above -#cpu_tuple_cost = 0.01 # same scale as above -#cpu_index_tuple_cost = 0.005 # same scale as above -#cpu_operator_cost = 0.0025 # same scale as above -#parallel_setup_cost = 1000.0 # same scale as above -#parallel_tuple_cost = 0.1 # same scale as above -#min_parallel_table_scan_size = 8MB -#min_parallel_index_scan_size = 512kB -effective_cache_size = 5963MB - -#jit_above_cost = 100000 # perform JIT compilation if available - # and query more expensive than this; - # -1 disables -#jit_inline_above_cost = 500000 # inline small functions if query is - # more expensive than this; -1 disables -#jit_optimize_above_cost = 500000 # use expensive JIT optimizations if - # query is more expensive than this; - # -1 disables - -# - Genetic Query Optimizer - - -#geqo = on -#geqo_threshold = 12 -#geqo_effort = 5 # range 1-10 -#geqo_pool_size = 0 # selects default based on effort -#geqo_generations = 0 # selects default based on effort -#geqo_selection_bias = 2.0 # range 1.5-2.0 -#geqo_seed = 0.0 # range 0.0-1.0 - -# - Other Planner Options - - -default_statistics_target = 500 # range 1-10000 -#constraint_exclusion = partition # on, off, or partition -#cursor_tuple_fraction = 0.1 # range 0.0-1.0 -#from_collapse_limit = 8 -#jit = on # allow JIT compilation -#join_collapse_limit = 8 # 1 disables collapsing of explicit - # JOIN clauses -#plan_cache_mode = auto # auto, force_generic_plan or - # force_custom_plan - -#------------------------------------------------------------------------------ -# REPORTING AND LOGGING -#------------------------------------------------------------------------------ - -# - Where to Log - - -#log_destination = 'stderr' # Valid values are combinations of - # stderr, csvlog, syslog, and eventlog, - # depending on platform. csvlog - # requires logging_collector to be on. - -# This is used when logging to stderr: -#logging_collector = off # Enable capturing of stderr and csvlog - # into log files. Required to be on for - # csvlogs. - # (change requires restart) - -# These are only used if logging_collector is on: -#log_directory = 'log' # directory where log files are written, - # can be absolute or relative to PGDATA -#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern, - # can include strftime() escapes -#log_file_mode = 0600 # creation mode for log files, - # begin with 0 to use octal notation -#log_rotation_age = 1d # Automatic rotation of logfiles will - # happen after that time. 0 disables. -#log_rotation_size = 10MB # Automatic rotation of logfiles will - # happen after that much log output. - # 0 disables. -#log_truncate_on_rotation = off # If on, an existing log file with the - # same name as the new log file will be - # truncated rather than appended to. - # But such truncation only occurs on - # time-driven rotation, not on restarts - # or size-driven rotation. Default is - # off, meaning append to existing files - # in all cases. - -# These are relevant when logging to syslog: -#syslog_facility = 'LOCAL0' -#syslog_ident = 'postgres' -#syslog_sequence_numbers = on -#syslog_split_messages = on - -# This is only relevant when logging to eventlog (Windows): -# (change requires restart) -#event_source = 'PostgreSQL' - -# - When to Log - - -#log_min_messages = warning # values in order of decreasing detail: - # debug5 - # debug4 - # debug3 - # debug2 - # debug1 - # info - # notice - # warning - # error - # log - # fatal - # panic - -#log_min_error_statement = error # values in order of decreasing detail: - # debug5 - # debug4 - # debug3 - # debug2 - # debug1 - # info - # notice - # warning - # error - # log - # fatal - # panic (effectively off) - -#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements - # and their durations, > 0 logs only - # statements running at least this number - # of milliseconds - -#log_min_duration_sample = -1 # -1 is disabled, 0 logs a sample of statements - # and their durations, > 0 logs only a sample of - # statements running at least this number - # of milliseconds; - # sample fraction is determined by log_statement_sample_rate - -#log_statement_sample_rate = 1.0 # fraction of logged statements exceeding - # log_min_duration_sample to be logged; - # 1.0 logs all such statements, 0.0 never logs - -#log_transaction_sample_rate = 0.0 # fraction of transactions whose statements - # are logged regardless of their duration; 1.0 logs all - # statements from all transactions, 0.0 never logs - -# - What to Log - - -#debug_print_parse = off -#debug_print_rewritten = off -#debug_print_plan = off -#debug_pretty_print = on -#log_autovacuum_min_duration = -1 # log autovacuum activity; - # -1 disables, 0 logs all actions and - # their durations, > 0 logs only - # actions running at least this number - # of milliseconds. -#log_checkpoints = off -#log_connections = off -#log_disconnections = off -#log_duration = off -#log_error_verbosity = default # terse, default, or verbose messages -#log_hostname = off -#log_line_prefix = '%m [%p] ' # special values: - # %a = application name - # %u = user name - # %d = database name - # %r = remote host and port - # %h = remote host - # %b = backend type - # %p = process ID - # %P = process ID of parallel group leader - # %t = timestamp without milliseconds - # %m = timestamp with milliseconds - # %n = timestamp with milliseconds (as a Unix epoch) - # %Q = query ID (0 if none or not computed) - # %i = command tag - # %e = SQL state - # %c = session ID - # %l = session line number - # %s = session start timestamp - # %v = virtual transaction ID - # %x = transaction ID (0 if none) - # %q = stop here in non-session - # processes - # %% = '%' - # e.g. '<%u%%%d> ' -#log_lock_waits = off # log lock waits >= deadlock_timeout -#log_recovery_conflict_waits = off # log standby recovery conflict waits - # >= deadlock_timeout -#log_parameter_max_length = -1 # when logging statements, limit logged - # bind-parameter values to N bytes; - # -1 means print in full, 0 disables -#log_parameter_max_length_on_error = 0 # when logging an error, limit logged - # bind-parameter values to N bytes; - # -1 means print in full, 0 disables -#log_statement = 'none' # none, ddl, mod, all -#log_replication_commands = off -#log_temp_files = -1 # log temporary files equal or larger - # than the specified size in kilobytes; - # -1 disables, 0 logs all temp files -log_timezone = 'UTC' - -#------------------------------------------------------------------------------ -# PROCESS TITLE -#------------------------------------------------------------------------------ - -#cluster_name = '' # added to process titles if nonempty - # (change requires restart) -#update_process_title = on - -#------------------------------------------------------------------------------ -# STATISTICS -#------------------------------------------------------------------------------ - -# - Query and Index Statistics Collector - - -#track_activities = on -#track_activity_query_size = 1024 # (change requires restart) -#track_counts = on -#track_io_timing = off -#track_wal_io_timing = off -#track_functions = none # none, pl, all -#stats_temp_directory = 'pg_stat_tmp' - -# - Monitoring - - -#compute_query_id = auto -#log_statement_stats = off -#log_parser_stats = off -#log_planner_stats = off -#log_executor_stats = off - -#------------------------------------------------------------------------------ -# AUTOVACUUM -#------------------------------------------------------------------------------ - -#autovacuum = on # Enable autovacuum subprocess? 'on' - # requires track_counts to also be on. -autovacuum_max_workers = 10 # max number of autovacuum subprocesses - # (change requires restart) -autovacuum_naptime = 10 # time between autovacuum runs -#autovacuum_vacuum_threshold = 50 # min number of row updates before - # vacuum -#autovacuum_vacuum_insert_threshold = 1000 # min number of row inserts - # before vacuum; -1 disables insert - # vacuums -#autovacuum_analyze_threshold = 50 # min number of row updates before - # analyze -#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum -#autovacuum_vacuum_insert_scale_factor = 0.2 # fraction of inserts over table - # size before insert vacuum -#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze -#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum - # (change requires restart) -#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age - # before forced vacuum - # (change requires restart) -#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for - # autovacuum, in milliseconds; - # -1 means use vacuum_cost_delay -#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for - # autovacuum, -1 means use - # vacuum_cost_limit - -#------------------------------------------------------------------------------ -# CLIENT CONNECTION DEFAULTS -#------------------------------------------------------------------------------ - -# - Statement Behavior - - -#client_min_messages = notice # values in order of decreasing detail: - # debug5 - # debug4 - # debug3 - # debug2 - # debug1 - # log - # notice - # warning - # error -#search_path = '"$user", public' # schema names -#row_security = on -#default_table_access_method = 'heap' -#default_tablespace = '' # a tablespace name, '' uses the default -#default_toast_compression = 'pglz' # 'pglz' or 'lz4' -#temp_tablespaces = '' # a list of tablespace names, '' uses - # only default tablespace -#check_function_bodies = on -#default_transaction_isolation = 'read committed' -#default_transaction_read_only = off -#default_transaction_deferrable = off -#session_replication_role = 'origin' -#statement_timeout = 0 # in milliseconds, 0 is disabled -#lock_timeout = 0 # in milliseconds, 0 is disabled -#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled -#idle_session_timeout = 0 # in milliseconds, 0 is disabled -#vacuum_freeze_table_age = 150000000 -#vacuum_freeze_min_age = 50000000 -#vacuum_failsafe_age = 1600000000 -#vacuum_multixact_freeze_table_age = 150000000 -#vacuum_multixact_freeze_min_age = 5000000 -#vacuum_multixact_failsafe_age = 1600000000 -#bytea_output = 'hex' # hex, escape -#xmlbinary = 'base64' -#xmloption = 'content' -#gin_pending_list_limit = 4MB - -# - Locale and Formatting - - -datestyle = 'iso, mdy' -#intervalstyle = 'postgres' -timezone = 'UTC' -#timezone_abbreviations = 'Default' # Select the set of available time zone - # abbreviations. Currently, there are - # Default - # Australia (historical usage) - # India - # You can create your own file in - # share/timezonesets/. -#extra_float_digits = 1 # min -15, max 3; any value >0 actually - # selects precise output mode -#client_encoding = sql_ascii # actually, defaults to database - # encoding - -# These settings are initialized by initdb, but they can be changed. -lc_messages = 'en_US.utf8' # locale for system error message - # strings -lc_monetary = 'en_US.utf8' # locale for monetary formatting -lc_numeric = 'en_US.utf8' # locale for number formatting -lc_time = 'en_US.utf8' # locale for time formatting - -# default configuration for text search -default_text_search_config = 'pg_catalog.english' - -# - Shared Library Preloading - - -#local_preload_libraries = '' -#session_preload_libraries = '' -shared_preload_libraries = 'timescaledb' # (change requires restart) -#jit_provider = 'llvmjit' # JIT library to use - -# - Other Defaults - - -#dynamic_library_path = '$libdir' -#gin_fuzzy_search_limit = 0 - -#------------------------------------------------------------------------------ -# LOCK MANAGEMENT -#------------------------------------------------------------------------------ - -#deadlock_timeout = 1s -max_locks_per_transaction = 64 # min 10 - # (change requires restart) -#max_pred_locks_per_transaction = 64 # min 10 - # (change requires restart) -#max_pred_locks_per_relation = -2 # negative values mean - # (max_pred_locks_per_transaction - # / -max_pred_locks_per_relation) - 1 -#max_pred_locks_per_page = 2 # min 0 - -#------------------------------------------------------------------------------ -# VERSION AND PLATFORM COMPATIBILITY -#------------------------------------------------------------------------------ - -# - Previous PostgreSQL Versions - - -#array_nulls = on -#backslash_quote = safe_encoding # on, off, or safe_encoding -#escape_string_warning = on -#lo_compat_privileges = off -#quote_all_identifiers = off -#standard_conforming_strings = on -#synchronize_seqscans = on - -# - Other Platforms and Clients - - -#transform_null_equals = off - -#------------------------------------------------------------------------------ -# ERROR HANDLING -#------------------------------------------------------------------------------ - -#exit_on_error = off # terminate session on any error? -#restart_after_crash = on # reinitialize after backend crash? -#data_sync_retry = off # retry or panic on failure to fsync - # data? - # (change requires restart) -#recovery_init_sync_method = fsync # fsync, syncfs (Linux 5.8+) - -#------------------------------------------------------------------------------ -# CONFIG FILE INCLUDES -#------------------------------------------------------------------------------ - -# These options allow settings to be loaded from files other than the -# default postgresql.conf. Note that these are directives, not variable -# assignments, so they can usefully be given more than once. - -#include_dir = '...' # include files ending in '.conf' from - # a directory, e.g., 'conf.d' -#include_if_exists = '...' # include file only if it exists -#include = '...' # include file - -#------------------------------------------------------------------------------ -# CUSTOMIZED OPTIONS -#------------------------------------------------------------------------------ - -# Add settings for extensions here -timescaledb.telemetry_level=basic -timescaledb.max_background_workers = 10 -timescaledb.last_tuned = '2022-08-17T07:21:10Z' -timescaledb.last_tuned_version = '0.12.0' diff --git a/db/query.sql b/db/query.sql index c2932e9..b650cfc 100644 --- a/db/query.sql +++ b/db/query.sql @@ -37,7 +37,7 @@ WHERE NOT EXISTS ( ); --- name: InsertSoraUserAgentStats :exec +-- name: InsertSoraRtcStats :exec WITH existing_record AS ( SELECT * FROM sora_user_agent_stats @@ -54,18 +54,35 @@ data_without_timestamp AS ( ) as new_data FROM existing_record ) -INSERT INTO sora_user_agent_stats ( - timestamp, +INSERT INTO sora_rtc_stats ( + label, + version, + node_name, + multistream, + simulcast, + spotlight, + role, channel_id, - connection_id, + session_id, + client_id, + connection_id rtc_stats_timestamp, rtc_stats_type, rtc_stats_id, rtc_stats_data ) SELECT @timestamp, + @label, + @version, + @node_name, + @multistream, + @simulcast, + @spotlight, + @role, @channel_id, - @connection_id, + @session_id, + @client_id, + @connection_id @rtc_stats_timestamp, @rtc_stats_type, @rtc_stats_id, @@ -90,18 +107,18 @@ LIMIT 1; SELECT count(*) FROM sora_connection; --- name: TestGetUserAgentStatsType :one +-- name: TestGetRtcStatsType :one SELECT rtc_stats_type -FROM sora_user_agent_stats +FROM sora_rtc_stats WHERE channel_id = @channel_id AND connection_id = @connection_id ORDER BY timestamp DESC LIMIT 1; -- 指定した channel_id と connection_id と rtc_stats_type のレコードがいくつあるかどうか --- name: TestGetUserAgentStatsTypeCount :one +-- name: TestGetRtcStatsTypeCount :one SELECT count(*) -FROM sora_user_agent_stats +FROM sora_rtc_stats WHERE rtc_stats_type = @rtc_type_stats AND channel_id = @channel_id AND connection_id = @connection_id; @@ -109,5 +126,5 @@ WHERE rtc_stats_type = @rtc_type_stats -- name: TestDropSoraConnection :exec DELETE FROM sora_connection; --- name: TestDropSoraUserAgentStats :exec -DELETE FROM sora_user_agent_stats; +-- name: TestDropSoraRtcStats :exec +DELETE FROM sora_rtc_stats; diff --git a/db/schema.sql b/db/schema.sql index e853529..570042d 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -1,57 +1,30 @@ -CREATE EXTENSION IF NOT EXISTS timescaledb; - -CREATE TYPE sora_connection_role AS ENUM ( - 'sendrecv', - 'sendonly', - 'recvonly' -); - -DROP TABLE IF EXISTS sora_connection; -CREATE TABLE IF NOT EXISTS sora_connection ( - pk bigserial NOT NULL PRIMARY KEY, - - -- Sora 側から送られてきたタイムスタンプ - timestamp timestamptz NOT NULL, - - version TEXT NOT NULL, - label TEXT NOT NULL, - node_name TEXT NOT NULL, - - multistream BOOLEAN NOT NULL, - simulcast BOOLEAN NOT NULL, - spotlight BOOLEAN NOT NULL, - - role sora_connection_role NOT NULL, - channel_id TEXT NOT NULL, - session_id TEXT NOT NULL, - client_id TEXT NOT NULL, - connection_id TEXT NOT NULL, - - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL -); - -DROP TABLE IF EXISTS sora_user_agent_stats; -CREATE TABLE IF NOT EXISTS sora_user_agent_stats ( - -- Sora 側から送られてきたタイムスタンプ - timestamp timestamptz NOT NULL, - - channel_id TEXT NOT NULL, - connection_id TEXT NOT NULL, - - rtc_stats_timestamp DOUBLE PRECISION NOT NULL, - rtc_stats_type TEXT NOT NULL, - rtc_stats_id TEXT NOT NULL, - - rtc_stats_data JSONB NOT NULL, - - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL -); -SELECT create_hypertable('sora_user_agent_stats', 'timestamp'); -ALTER TABLE sora_user_agent_stats SET ( - timescaledb.compress, - timescaledb.compress_segmentby = 'channel_id, connection_id' -); --- 圧縮の INTERNVAL の値は自由に変えること -SELECT add_compression_policy('sora_user_agent_stats', INTERVAL '7 days'); --- 保持の INTERNVAL の値は自由に変えること -SELECT add_retention_policy('sora_user_agent_stats', INTERVAL '60 days'); \ No newline at end of file +SET allow_experimental_object_type = 1; + +DROP TABLE IF EXISTS sora_rtc_stats; +CREATE TABLE IF NOT EXISTS sora_rtc_stats ( + timestamp DateTime64(3, 'UTC'), + + version String, + label String, + node_name String, + + multistream UInt8, + simulcast UInt8, + spotlight UInt8, + + role Enum8('sendrecv' = 1, 'sendonly' = 2, 'recvonly' = 3), + + channel_id String, + session_id String, + client_id String, + connection_id String, + + rtc_stats_timestamp Float64, + rtc_stats_type String, + rtc_stats_id String, + + rtc_stats_data JSON, + + created_at DateTime64(3, 'UTC') DEFAULT now() +) ENGINE = MergeTree() +PRIMARY KEY (connection_id, timestamp) diff --git a/db/test.sql b/db/test.sql new file mode 100644 index 0000000..94c4c49 --- /dev/null +++ b/db/test.sql @@ -0,0 +1,47 @@ +INSERT INTO sora_rtc_stats ( + timestamp, + + version, + label, + node_name, + + multistream, + simulcast, + spotlight, + + role, + + channel_id, + session_id, + client_id, + connection_id, + + rtc_stats_timestamp, + rtc_stats_type, + rtc_stats_id, + + rtc_stats_data +) VALUES ( + now(), + + '2024.1.0', + 'label', + 'sora@192.0.2.1', + + true, + false, + false, + + 'sendrecv', + + 'channel-id', + 'session-id', + 'client-id', + 'connection-id', + + 900000.1000, + 'inbound-rtp', + 'ID', + + '{"a": 1, "b": { "c": 2, "d": [1, 2, 3] }}' +); \ No newline at end of file diff --git a/db/test/sora_connection_test.go b/db/test/sora_connection_test.go deleted file mode 100644 index e5d7d2f..0000000 --- a/db/test/sora_connection_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package sqlc - -import ( - "context" - "testing" - "time" - - db "github.com/shiguredo/kohaku/gen/sqlc" - "github.com/stretchr/testify/assert" -) - -func TestSoraConnection(t *testing.T) { - c := context.Background() - var err error - - channelID := "channel_id" - sessionID := base32edUUIDv4() - connectionID := base32edUUIDv4() - - err = q.InsertSoraConnection(c, db.InsertSoraConnectionParams{ - Timestamp: time.Now().UTC(), - Label: "label", - Version: "version", - NodeName: "node_name", - Multistream: true, - Simulcast: true, - Spotlight: true, - Role: db.SoraConnectionRoleSendrecv, - ChannelID: channelID, - SessionID: sessionID, - ClientID: "client_id", - ConnectionID: connectionID, - }) - assert.NoError(t, err) - - sc, err := q.TestGetSoraConnection(c, db.TestGetSoraConnectionParams{ - ChannelID: "channel_id", - ConnectionID: connectionID, - }) - assert.NoError(t, err) - assert.Equal(t, "label", sc.Label) - assert.Equal(t, db.SoraConnectionRoleSendrecv, sc.Role) - - count, err := q.TestGetSoraConnectionCount(c) - assert.NoError(t, err) - assert.EqualValues(t, 1, count) - - // 同じものを追加 - err = q.InsertSoraConnection(c, db.InsertSoraConnectionParams{ - Timestamp: time.Now().UTC(), - Label: "label", - Version: "version", - NodeName: "node_name", - Multistream: true, - Simulcast: true, - Spotlight: true, - Role: db.SoraConnectionRoleSendrecv, - ChannelID: channelID, - SessionID: sessionID, - ClientID: "client_id", - ConnectionID: connectionID, - }) - assert.NoError(t, err) - - // 1 そのまま - count, err = q.TestGetSoraConnectionCount(c) - assert.NoError(t, err) - assert.EqualValues(t, 1, count) - - newConnectionID := base32edUUIDv4() - - // コネクション ID だけ新しくする - err = q.InsertSoraConnection(c, db.InsertSoraConnectionParams{ - Timestamp: time.Now().UTC(), - Label: "label", - Version: "version", - NodeName: "node_name", - Multistream: true, - Simulcast: true, - Spotlight: true, - Role: db.SoraConnectionRoleSendrecv, - ChannelID: channelID, - SessionID: sessionID, - ClientID: "client_id", - ConnectionID: newConnectionID, - }) - assert.NoError(t, err) - - // 2 になる - count, err = q.TestGetSoraConnectionCount(c) - assert.NoError(t, err) - assert.EqualValues(t, 2, count) - - err = q.TestDropSoraConnection(c) - assert.NoError(t, err) -} diff --git a/db/test/sora_user_agent_stats_test.go b/db/test/sora_user_agent_stats_test.go deleted file mode 100644 index 63f44dc..0000000 --- a/db/test/sora_user_agent_stats_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package sqlc - -import ( - "context" - "testing" - "time" - - "github.com/jackc/pgtype" - db "github.com/shiguredo/kohaku/gen/sqlc" - "github.com/stretchr/testify/assert" -) - -func TestSoraUserAgentStats(t *testing.T) { - c := context.Background() - var err error - - channelID := "channel_id" - connectionID := base32edUUIDv4() - - rawStatsData := []byte(`{"timestamp": 1683605052194.28, "id": "UUIDv4", "type": "outbound-rtp"}`) - statsData := pgtype.JSONB{} - statsData.Set(rawStatsData) - - err = q.InsertSoraUserAgentStats(c, db.InsertSoraUserAgentStatsParams{ - Timestamp: time.Now().UTC(), - ChannelID: channelID, - ConnectionID: connectionID, - RtcStatsTimestamp: 1683605052194.865, - RtcStatsType: "outbound-rtp", - RtcStatsID: "UUIDv4", - RtcStatsData: statsData, - }) - assert.NoError(t, err) - - count, err := q.TestGetUserAgentStatsTypeCount(c, db.TestGetUserAgentStatsTypeCountParams{ - RtcTypeStats: "outbound-rtp", - ChannelID: channelID, - ConnectionID: connectionID, - }) - assert.NoError(t, err) - assert.EqualValues(t, 1, count) - - // 時間だけが違うやつ - err = q.InsertSoraUserAgentStats(c, db.InsertSoraUserAgentStatsParams{ - Timestamp: time.Now().UTC(), - ChannelID: channelID, - ConnectionID: connectionID, - RtcStatsTimestamp: 1683605052200.000, - RtcStatsType: "outbound-rtp", - RtcStatsID: "UUIDv4", - RtcStatsData: statsData, - }) - assert.NoError(t, err) - - // 時間だけ違うのでレコードは増えない - count, err = q.TestGetUserAgentStatsTypeCount(c, db.TestGetUserAgentStatsTypeCountParams{ - RtcTypeStats: "outbound-rtp", - ChannelID: channelID, - ConnectionID: connectionID, - }) - assert.NoError(t, err) - assert.EqualValues(t, 1, count) - - // a: b が追加されている - rawNewStatsData := []byte(`{"timestamp": 1683605052194.28, "id": "UUIDv4", "type": "outbound-rtp", "a": "b"}`) - newStatsData := pgtype.JSONB{} - newStatsData.Set(rawNewStatsData) - - // 時間が違い、 a: b が追加されているのでレコードが増える - err = q.InsertSoraUserAgentStats(c, db.InsertSoraUserAgentStatsParams{ - Timestamp: time.Now().UTC(), - ChannelID: channelID, - ConnectionID: connectionID, - RtcStatsTimestamp: 1683605052200.500, - RtcStatsType: "outbound-rtp", - RtcStatsID: "UUIDv4", - RtcStatsData: newStatsData, - }) - assert.NoError(t, err) - - // レコードが増える - count, err = q.TestGetUserAgentStatsTypeCount(c, db.TestGetUserAgentStatsTypeCountParams{ - RtcTypeStats: "outbound-rtp", - ChannelID: channelID, - ConnectionID: connectionID, - }) - assert.NoError(t, err) - assert.EqualValues(t, 2, count) - - err = q.TestDropSoraUserAgentStats(c) - assert.NoError(t, err) -} diff --git a/db/test/sqlc_test.go b/db/test/sqlc_test.go deleted file mode 100644 index 515d156..0000000 --- a/db/test/sqlc_test.go +++ /dev/null @@ -1,138 +0,0 @@ -package sqlc - -import ( - "context" - "fmt" - "log" - "os" - "os/signal" - "path/filepath" - "runtime" - "syscall" - "testing" - "time" - - "github.com/google/uuid" - "github.com/jackc/pgx/v4/pgxpool" - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" - - db "github.com/shiguredo/kohaku/gen/sqlc" - base32 "github.com/shogo82148/go-clockwork-base32" -) - -// query テスト用 -var q *db.Queries - -// transaction テスト用 -var pp *pgxpool.Pool - -func base32edUUIDv4() string { - id := uuid.New() - binaryUUID, _ := id.MarshalBinary() - return base32.NewEncoding().EncodeToString(binaryUUID) -} - -func TestMain(m *testing.M) { - pool, err := dockertest.NewPool("") - pool.MaxWait = 10 * time.Second - if err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - - // https://qiita.com/daijinload/items/f6cc602d64f8397faea5 - _, b, _, _ := runtime.Caller(0) - root := filepath.Join(filepath.Dir(b), "../../../") - - containerName := "test" - - // ./db/schema.sql のパスを取得する - schemaPath := filepath.Join(root, "kohaku", "db", "schema.sql") - schemaFilename := filepath.Base(schemaPath) - mountFiles := []string{} - mountPath := fmt.Sprintf("%s:/docker-entrypoint-initdb.d/%s", schemaPath, schemaFilename) - mountFiles = append(mountFiles, mountPath) - runOptions := &dockertest.RunOptions{ - Repository: "timescale/timescaledb", - Tag: "latest-pg15", - Env: []string{ - "POSTGRES_USER=postgres", - "POSTGRES_PASSWORD=password", - "POSTGRES_DB=dockertest", - "listen_addresses='*'", - }, - Mounts: mountFiles, - Name: containerName, - } - - resource, err := pool.RunWithOptions(runOptions, - func(config *docker.HostConfig) { - config.AutoRemove = true - config.RestartPolicy = docker.RestartPolicy{ - Name: "no", - } - }, - ) - if err != nil { - log.Fatalf("Could not start resource: %s", err) - } - - done := make(chan struct{}) - defer close(done) - ch := make(chan struct{}) - defer close(ch) - - go func() { - s := make(chan os.Signal, 1) - signal.Notify(s, syscall.SIGINT) - - var d bool - select { - case <-done: - d = true - case <-s: - } - - if err := pool.Purge(resource); err != nil { - log.Fatalf("Could not purge resource: %s", err) - } - - if d { - ch <- struct{}{} - } else { - os.Exit(0) - } - }() - - port := resource.GetPort("5432/tcp") - databaseURL := fmt.Sprintf("postgres://postgres:password@127.0.0.1:%s/dockertest?sslmode=disable", port) - - if err := pool.Retry(func() error { - config, err := pgxpool.ParseConfig(databaseURL) - if err != nil { - return err - } - - p, err := pgxpool.ConnectConfig(context.Background(), config) - if err != nil { - return err - } - - if err := p.Ping(context.Background()); err != nil { - return err - } - - q = db.New(p) - pp = p - return nil - }); err != nil { - log.Fatalf("Could not connect to database: %s", err) - } - - code := m.Run() - - done <- struct{}{} - <-ch - - os.Exit(code) -} diff --git a/gen/sqlc/db.go b/gen/sqlc/db.go deleted file mode 100644 index fbd0074..0000000 --- a/gen/sqlc/db.go +++ /dev/null @@ -1,32 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.24.0 - -package db - -import ( - "context" - - "github.com/jackc/pgconn" - "github.com/jackc/pgx/v4" -) - -type DBTX interface { - Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) - Query(context.Context, string, ...interface{}) (pgx.Rows, error) - QueryRow(context.Context, string, ...interface{}) pgx.Row -} - -func New(db DBTX) *Queries { - return &Queries{db: db} -} - -type Queries struct { - db DBTX -} - -func (q *Queries) WithTx(tx pgx.Tx) *Queries { - return &Queries{ - db: tx, - } -} diff --git a/gen/sqlc/models.go b/gen/sqlc/models.go deleted file mode 100644 index 630324a..0000000 --- a/gen/sqlc/models.go +++ /dev/null @@ -1,84 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.24.0 - -package db - -import ( - "database/sql/driver" - "fmt" - "time" - - "github.com/jackc/pgtype" -) - -type SoraConnectionRole string - -const ( - SoraConnectionRoleSendrecv SoraConnectionRole = "sendrecv" - SoraConnectionRoleSendonly SoraConnectionRole = "sendonly" - SoraConnectionRoleRecvonly SoraConnectionRole = "recvonly" -) - -func (e *SoraConnectionRole) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = SoraConnectionRole(s) - case string: - *e = SoraConnectionRole(s) - default: - return fmt.Errorf("unsupported scan type for SoraConnectionRole: %T", src) - } - return nil -} - -type NullSoraConnectionRole struct { - SoraConnectionRole SoraConnectionRole `json:"sora_connection_role"` - Valid bool `json:"valid"` // Valid is true if SoraConnectionRole is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullSoraConnectionRole) Scan(value interface{}) error { - if value == nil { - ns.SoraConnectionRole, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.SoraConnectionRole.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullSoraConnectionRole) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.SoraConnectionRole), nil -} - -type SoraConnection struct { - Pk int64 `json:"pk"` - Timestamp time.Time `json:"timestamp"` - Version string `json:"version"` - Label string `json:"label"` - NodeName string `json:"node_name"` - Multistream bool `json:"multistream"` - Simulcast bool `json:"simulcast"` - Spotlight bool `json:"spotlight"` - Role SoraConnectionRole `json:"role"` - ChannelID string `json:"channel_id"` - SessionID string `json:"session_id"` - ClientID string `json:"client_id"` - ConnectionID string `json:"connection_id"` - CreatedAt time.Time `json:"created_at"` -} - -type SoraUserAgentStats struct { - Timestamp time.Time `json:"timestamp"` - ChannelID string `json:"channel_id"` - ConnectionID string `json:"connection_id"` - RtcStatsTimestamp float64 `json:"rtc_stats_timestamp"` - RtcStatsType string `json:"rtc_stats_type"` - RtcStatsID string `json:"rtc_stats_id"` - RtcStatsData pgtype.JSONB `json:"rtc_stats_data"` - CreatedAt time.Time `json:"created_at"` -} diff --git a/gen/sqlc/query.sql.go b/gen/sqlc/query.sql.go deleted file mode 100644 index 0c43f30..0000000 --- a/gen/sqlc/query.sql.go +++ /dev/null @@ -1,258 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.24.0 -// source: query.sql - -package db - -import ( - "context" - "time" - - "github.com/jackc/pgtype" -) - -const InsertSoraConnection = `-- name: InsertSoraConnection :exec -INSERT INTO sora_connection ( - timestamp, - label, - version, - node_name, - multistream, - simulcast, - spotlight, - role, - channel_id, - session_id, - client_id, - connection_id - ) -SELECT $1, - $2, - $3, - $4, - $5, - $6, - $7, - $8, - $9, - $10, - $11, - $12 -WHERE NOT EXISTS ( - SELECT 1 - FROM sora_connection - WHERE ( - (channel_id = $9::text) - AND (session_id = $10::text) - AND (client_id = $11::text) - AND (connection_id = $12::text) - ) - ) -` - -type InsertSoraConnectionParams struct { - Timestamp time.Time `json:"timestamp"` - Label string `json:"label"` - Version string `json:"version"` - NodeName string `json:"node_name"` - Multistream bool `json:"multistream"` - Simulcast bool `json:"simulcast"` - Spotlight bool `json:"spotlight"` - Role SoraConnectionRole `json:"role"` - ChannelID string `json:"channel_id"` - SessionID string `json:"session_id"` - ClientID string `json:"client_id"` - ConnectionID string `json:"connection_id"` -} - -func (q *Queries) InsertSoraConnection(ctx context.Context, arg InsertSoraConnectionParams) error { - _, err := q.db.Exec(ctx, InsertSoraConnection, - arg.Timestamp, - arg.Label, - arg.Version, - arg.NodeName, - arg.Multistream, - arg.Simulcast, - arg.Spotlight, - arg.Role, - arg.ChannelID, - arg.SessionID, - arg.ClientID, - arg.ConnectionID, - ) - return err -} - -const InsertSoraUserAgentStats = `-- name: InsertSoraUserAgentStats :exec -WITH existing_record AS ( - SELECT timestamp, channel_id, connection_id, rtc_stats_timestamp, rtc_stats_type, rtc_stats_id, rtc_stats_data, created_at - FROM sora_user_agent_stats - WHERE sora_user_agent_stats.channel_id = $2 - AND sora_user_agent_stats.connection_id = $3 - AND sora_user_agent_stats.rtc_stats_type = $5 - AND sora_user_agent_stats.rtc_stats_id = $6 -), -data_without_timestamp AS ( - SELECT existing_record.rtc_stats_data - 'timestamp' as old_data, - -- @rtc_stats_data - 'timestamp' as new_data は sqlc generate でエラーになるため jsonb_strip_nulls を残す - jsonb_strip_nulls( - jsonb_set($7, '{timestamp}', 'null') - ) as new_data - FROM existing_record -) -INSERT INTO sora_user_agent_stats ( - timestamp, - channel_id, - connection_id, - rtc_stats_timestamp, - rtc_stats_type, - rtc_stats_id, - rtc_stats_data - ) -SELECT $1, - $2, - $3, - $4, - $5, - $6, - $7 -WHERE NOT EXISTS ( - SELECT 1 - FROM data_without_timestamp - WHERE data_without_timestamp.old_data = data_without_timestamp.new_data -) -` - -type InsertSoraUserAgentStatsParams struct { - Timestamp time.Time `json:"timestamp"` - ChannelID string `json:"channel_id"` - ConnectionID string `json:"connection_id"` - RtcStatsTimestamp float64 `json:"rtc_stats_timestamp"` - RtcStatsType string `json:"rtc_stats_type"` - RtcStatsID string `json:"rtc_stats_id"` - RtcStatsData pgtype.JSONB `json:"rtc_stats_data"` -} - -func (q *Queries) InsertSoraUserAgentStats(ctx context.Context, arg InsertSoraUserAgentStatsParams) error { - _, err := q.db.Exec(ctx, InsertSoraUserAgentStats, - arg.Timestamp, - arg.ChannelID, - arg.ConnectionID, - arg.RtcStatsTimestamp, - arg.RtcStatsType, - arg.RtcStatsID, - arg.RtcStatsData, - ) - return err -} - -const TestDropSoraConnection = `-- name: TestDropSoraConnection :exec -DELETE FROM sora_connection -` - -func (q *Queries) TestDropSoraConnection(ctx context.Context) error { - _, err := q.db.Exec(ctx, TestDropSoraConnection) - return err -} - -const TestDropSoraUserAgentStats = `-- name: TestDropSoraUserAgentStats :exec -DELETE FROM sora_user_agent_stats -` - -func (q *Queries) TestDropSoraUserAgentStats(ctx context.Context) error { - _, err := q.db.Exec(ctx, TestDropSoraUserAgentStats) - return err -} - -const TestGetSoraConnection = `-- name: TestGetSoraConnection :one - -SELECT pk, timestamp, version, label, node_name, multistream, simulcast, spotlight, role, channel_id, session_id, client_id, connection_id, created_at -FROM sora_connection -WHERE channel_id = $1 - AND connection_id = $2 -LIMIT 1 -` - -type TestGetSoraConnectionParams struct { - ChannelID string `json:"channel_id"` - ConnectionID string `json:"connection_id"` -} - -// test query -func (q *Queries) TestGetSoraConnection(ctx context.Context, arg TestGetSoraConnectionParams) (SoraConnection, error) { - row := q.db.QueryRow(ctx, TestGetSoraConnection, arg.ChannelID, arg.ConnectionID) - var i SoraConnection - err := row.Scan( - &i.Pk, - &i.Timestamp, - &i.Version, - &i.Label, - &i.NodeName, - &i.Multistream, - &i.Simulcast, - &i.Spotlight, - &i.Role, - &i.ChannelID, - &i.SessionID, - &i.ClientID, - &i.ConnectionID, - &i.CreatedAt, - ) - return i, err -} - -const TestGetSoraConnectionCount = `-- name: TestGetSoraConnectionCount :one -SELECT count(*) -FROM sora_connection -` - -func (q *Queries) TestGetSoraConnectionCount(ctx context.Context) (int64, error) { - row := q.db.QueryRow(ctx, TestGetSoraConnectionCount) - var count int64 - err := row.Scan(&count) - return count, err -} - -const TestGetUserAgentStatsType = `-- name: TestGetUserAgentStatsType :one -SELECT rtc_stats_type -FROM sora_user_agent_stats -WHERE channel_id = $1 - AND connection_id = $2 -ORDER BY timestamp DESC -LIMIT 1 -` - -type TestGetUserAgentStatsTypeParams struct { - ChannelID string `json:"channel_id"` - ConnectionID string `json:"connection_id"` -} - -func (q *Queries) TestGetUserAgentStatsType(ctx context.Context, arg TestGetUserAgentStatsTypeParams) (string, error) { - row := q.db.QueryRow(ctx, TestGetUserAgentStatsType, arg.ChannelID, arg.ConnectionID) - var rtc_stats_type string - err := row.Scan(&rtc_stats_type) - return rtc_stats_type, err -} - -const TestGetUserAgentStatsTypeCount = `-- name: TestGetUserAgentStatsTypeCount :one -SELECT count(*) -FROM sora_user_agent_stats -WHERE rtc_stats_type = $1 - AND channel_id = $2 - AND connection_id = $3 -` - -type TestGetUserAgentStatsTypeCountParams struct { - RtcTypeStats string `json:"rtc_type_stats"` - ChannelID string `json:"channel_id"` - ConnectionID string `json:"connection_id"` -} - -// 指定した channel_id と connection_id と rtc_stats_type のレコードがいくつあるかどうか -func (q *Queries) TestGetUserAgentStatsTypeCount(ctx context.Context, arg TestGetUserAgentStatsTypeCountParams) (int64, error) { - row := q.db.QueryRow(ctx, TestGetUserAgentStatsTypeCount, arg.RtcTypeStats, arg.ChannelID, arg.ConnectionID) - var count int64 - err := row.Scan(&count) - return count, err -} diff --git a/go.mod b/go.mod index cc41cdf..81c6dd9 100644 --- a/go.mod +++ b/go.mod @@ -1,84 +1,100 @@ module github.com/shiguredo/kohaku -go 1.22.2 +go 1.22.4 require ( - github.com/go-playground/validator/v10 v10.19.0 - github.com/google/uuid v1.6.0 - github.com/jackc/pgconn v1.14.3 - github.com/jackc/pgtype v1.14.3 - github.com/jackc/pgx/v4 v4.18.3 + github.com/ClickHouse/clickhouse-go/v2 v2.25.0 + github.com/go-playground/validator/v10 v10.22.0 github.com/labstack/echo-contrib v0.17.1 github.com/labstack/echo/v4 v4.12.0 - github.com/rs/zerolog v1.32.0 - github.com/shogo82148/go-clockwork-base32 v1.1.0 - golang.org/x/net v0.24.0 + github.com/rs/zerolog v1.33.0 golang.org/x/sync v0.7.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) require ( - github.com/ory/dockertest/v3 v3.10.0 github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.31.0 + github.com/testcontainers/testcontainers-go/modules/clickhouse v0.31.0 ) require ( + dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/ClickHouse/ch-go v0.61.5 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Microsoft/hcsshim v0.12.4 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/containerd/continuity v0.4.1 // indirect + github.com/containerd/containerd v1.7.18 // indirect + github.com/containerd/errdefs v0.1.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v24.0.5+incompatible // indirect - github.com/docker/docker v24.0.7+incompatible // indirect - github.com/docker/go-connections v0.4.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.0.0+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/gabriel-vasile/mimetype v1.4.4 // indirect + github.com/go-faster/city v1.0.1 // indirect + github.com/go-faster/errors v0.7.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/imdario/mergo v0.3.16 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgio v1.0.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.3 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/puddle v1.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/user v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.1.8 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/paulmach/orb v0.11.1 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.19.0 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.53.0 // indirect - github.com/prometheus/procfs v0.13.0 // indirect + github.com/prometheus/common v0.54.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/segmentio/asm v1.2.0 // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tklauser/go-sysconf v0.3.14 // indirect + github.com/tklauser/numcpus v0.8.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect + go.opentelemetry.io/otel v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.27.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.11.1 // indirect - google.golang.org/protobuf v1.33.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 // indirect + google.golang.org/grpc v1.64.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5ec8089..2a619d7 100644 --- a/go.sum +++ b/go.sum @@ -1,469 +1,288 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4= +github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg= +github.com/ClickHouse/clickhouse-go/v2 v2.25.0 h1:rKscwqgQHzWBTZySZDcHKxgs0Ad+xFULfZvo26W5UlY= +github.com/ClickHouse/clickhouse-go/v2 v2.25.0/go.mod h1:iDTViXk2Fgvf1jn2dbJd1ys+fBkdD1UMRnXlwmhijhQ= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/hcsshim v0.12.4 h1:Ev7YUMHAHoWNm+aDSPzc5W9s6E2jyL1szpVDJeZ/Rr4= +github.com/Microsoft/hcsshim v0.12.4/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= 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/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/containerd/continuity v0.4.1 h1:wQnVrjIyQ8vhU2sgOiL5T07jo+ouqc2bnKsv5/EqGhU= -github.com/containerd/continuity v0.4.1/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= +github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= +github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= +github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/docker/cli v24.0.5+incompatible h1:WeBimjvS0eKdH4Ygx+ihVq1Q++xg36M/rMi4aXAvodc= -github.com/docker/cli v24.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.0.0+incompatible h1:JRugTYuelmWlW0M3jakcIadDx2HUoUO6+Tf2C5jVfwA= +github.com/docker/docker v27.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= +github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= +github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= +github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= +github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= +github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= -github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= -github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U= -github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= -github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= -github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= -github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= -github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= -github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= -github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgtype v1.14.1 h1:LyDar7M2K0tShCWqzJ/ctzF1QC3Wzc9c8a6cHE0PFdc= -github.com/jackc/pgtype v1.14.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgtype v1.14.2 h1:QBdZQTKpPdBlw2AdKwHEyqUcm/lrl2cwWAHjCMyln/o= -github.com/jackc/pgtype v1.14.2/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgtype v1.14.3 h1:h6W9cPuHsRWQFTWUZMAKMgG5jSwQI0Zurzdvlx3Plus= -github.com/jackc/pgtype v1.14.3/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= -github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= -github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= -github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= -github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/labstack/echo-contrib v0.15.0 h1:9K+oRU265y4Mu9zpRDv3X+DGTqUALY6oRHCSZZKCRVU= -github.com/labstack/echo-contrib v0.15.0/go.mod h1:lei+qt5CLB4oa7VHTE0yEfQSEB9XTJI1LUqko9UWvo4= -github.com/labstack/echo-contrib v0.16.0 h1:vk5Kd+egpTOJxD3l+3IvZzQWPbrXiYxhkkgkJL99j/w= -github.com/labstack/echo-contrib v0.16.0/go.mod h1:mjX5VB3OqJcroIEycptBOY9Hr7rK+unq79W8QFKGNV0= github.com/labstack/echo-contrib v0.17.1 h1:7I/he7ylVKsDUieaGRZ9XxxTYOjfQwVzHzUYrNykfCU= github.com/labstack/echo-contrib v0.17.1/go.mod h1:SnsCZtwHBAZm5uBSAtQtXQHI3wqEA73hvTn0bYMKnZA= -github.com/labstack/echo/v4 v4.11.3 h1:Upyu3olaqSHkCjs1EJJwQ3WId8b8b1hxbogyommKktM= -github.com/labstack/echo/v4 v4.11.3/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws= -github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= -github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.1.8 h1:zICRlc+C1XzivLc3nzE+cbJV4LIi8tib6YG0MqC6OqA= -github.com/opencontainers/runc v1.1.8/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= -github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= -github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= +github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= +github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= -github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= -github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= -github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= 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.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/common v0.50.0 h1:YSZE6aa9+luNa2da6/Tik0q0A5AbR+U003TItK57CPQ= -github.com/prometheus/common v0.50.0/go.mod h1:wHFBCEVWVmHMUpg7pYcOm2QUR/ocQdYSJVQJKnHc3xQ= -github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= -github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= -github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= -github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= -github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= -github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8= +github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ= +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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= -github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= -github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/shogo82148/go-clockwork-base32 v1.1.0 h1:PEqSTiyVEKdl5ar5RHlUQjFJyJTx9XZD5dUVCLG08K0= -github.com/shogo82148/go-clockwork-base32 v1.1.0/go.mod h1:iMSFoMZCgiXZbDyIpEjQxr4T9dH5q8agqxt9HECoiEQ= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U= +github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI= +github.com/testcontainers/testcontainers-go/modules/clickhouse v0.31.0 h1:105EZAwt9bMBYEP8gsEp/DDP1St+2C5owXRMBrzN5M8= +github.com/testcontainers/testcontainers-go/modules/clickhouse v0.31.0/go.mod h1:k3Ci/1vTkugSX67/oU0lOHfuTPpSLKw5KMg6d61uESI= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= +github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= +github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= +github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc= -golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 h1:9Xyg6I9IWQZhRVfCWjKK+l6kI0jHcPesVlMnT//aHNo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= -gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/main_test.go b/main_test.go index 93148ec..83fed5f 100644 --- a/main_test.go +++ b/main_test.go @@ -2,28 +2,28 @@ package kohaku import ( "context" - "fmt" + "log" "os" "testing" - "time" - "github.com/jackc/pgx/v4/pgxpool" - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" - db "github.com/shiguredo/kohaku/gen/sqlc" + ch "github.com/ClickHouse/clickhouse-go/v2" + "github.com/ClickHouse/clickhouse-go/v2/lib/driver" + "github.com/stretchr/testify/assert" + "github.com/testcontainers/testcontainers-go" + + "github.com/testcontainers/testcontainers-go/modules/clickhouse" ) const ( - connStr = "postgres://%s:%s@%s/%s?sslmode=disable" - postgresUser = "postgres" - postgresPassword = "password" - postgresDB = "kohakutest" - - port = 15890 + clickhouseUser = "default" + clickhousePassword = "default" + clickhouseDB = "default" ) var ( - pgPool *pgxpool.Pool + conn *driver.Conn + + addr []string server *Server config = &Config{ @@ -32,75 +32,111 @@ var ( TLSVerifyCacertPath: "cert/client/ca.pem", HTTPS: true, ListenAddr: "0.0.0.0", - ListenPort: port, + ListenPort: 15890, } ) +// https://github.com/rilldata/rill/blob/65e7cb07060deab31ef1fef32e345d64d9611cb4/runtime/queries/metricsview_toplist_test.go#L25 + func TestMain(m *testing.M) { - pool, err := dockertest.NewPool("") + ctx := context.Background() + + clickHouseContainer, err := clickhouse.RunContainer(ctx, + testcontainers.WithImage("clickhouse/clickhouse-server:latest"), + clickhouse.WithUsername(clickhouseUser), + clickhouse.WithPassword(clickhousePassword), + clickhouse.WithDatabase(clickhouseDB), + ) if err != nil { - panic(err) + log.Fatalf("failed to start container: %s", err) } - pwd, _ := os.Getwd() + defer func() { + if err := clickHouseContainer.Terminate(ctx); err != nil { + log.Fatalf("failed to terminate container: %s", err) + } + }() - resource, err := pool.RunWithOptions(&dockertest.RunOptions{ - Repository: "timescale/timescaledb", - Tag: "latest-pg15", - Env: []string{ - "POSTGRES_PASSWORD=" + postgresPassword, - "POSTGRES_USER=" + postgresUser, - "POSTGRES_DB=" + postgresDB, - "listen_addresses = '*'", - }, - Mounts: []string{ - pwd + "/db/schema.sql:/docker-entrypoint-initdb.d/schema.sql", + // state, err := clickHouseContainer.State(ctx) + // if err != nil { + // log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + // } + + host, err := clickHouseContainer.Host(ctx) + if err != nil { + log.Fatalf("failed to get container host: %s", err) + } + port, err := clickHouseContainer.MappedPort(ctx, "9000") + if err != nil { + log.Fatalf("failed to get container port: %s", err) + } + + addr = []string{host + ":" + port.Port()} + conn, err := ch.Open(&ch.Options{ + Addr: addr, + Auth: ch.Auth{ + Username: clickhouseUser, + Password: clickhousePassword, + Database: clickhouseDB, }, - }, func(config *docker.HostConfig) { - config.AutoRemove = true - config.RestartPolicy = docker.RestartPolicy{Name: "no"} }) if err != nil { - panic(err) + log.Fatalf("failed to open ClickHouse connection: %s", err) } - hostAndPort := resource.GetHostPort("5432/tcp") - dbURI := fmt.Sprintf(connStr, postgresUser, postgresPassword, hostAndPort, postgresDB) - config.PostgresURI = dbURI - - resource.Expire(60) - pool.MaxWait = 60 * time.Second - if err = pool.Retry(func() error { - config, err := pgxpool.ParseConfig(dbURI) - if err != nil { - return err - } - pgPool, err = pgxpool.ConnectConfig(context.Background(), config) - if err != nil { - return err - } - - return pgPool.Ping(context.Background()) - }); err != nil { - panic(err) + err = conn.Ping(ctx) + if err != nil { + log.Fatalf("failed to ping ClickHouse: %s", err) } - server = newTestServer(config, pgPool) + // ここでテストを実行する code := m.Run() - if err := pool.Purge(resource); err != nil { - panic(err) - } + // 処理が終わったら、deferによってコンテナが自動的に停止して削除される os.Exit(code) } -func newTestServer(c *Config, pool *pgxpool.Pool) *Server { +func open() driver.Conn { + conn, err := ch.Open(&ch.Options{ + Addr: addr, + Auth: ch.Auth{ + Username: clickhouseUser, + Password: clickhousePassword, + Database: clickhouseDB, + }, + }) + if err != nil { + log.Fatalf("failed to open ClickHouse connection: %s", err) + } + + return conn +} + +func TestDummy(t *testing.T) { + ctx := context.Background() + var err error + + conn := open() + + err = conn.Exec(ctx, "CREATE TABLE IF NOT EXISTS test (id UInt32, name String) ENGINE = Memory") + assert.NoError(t, err) + + err = conn.Exec(ctx, "INSERT INTO test (id, name) VALUES (1, 'Alice')") + assert.NoError(t, err) + + rows, err := conn.Query(ctx, "SELECT * FROM test") + assert.NoError(t, err) + assert.Len(t, rows.Columns(), 2) + + log.Println("ダミーのテストが実行されました。") +} + +func newTestServer(c *Config, conn driver.Conn) *Server { s := &Server{ config: c, - pool: pool, - query: db.New(pool), + conn: conn, } s.setupEchoServer() diff --git a/ok_h.go b/ok_h.go index 8abe22d..addffaa 100644 --- a/ok_h.go +++ b/ok_h.go @@ -10,7 +10,7 @@ import ( // TODO: ログレベル、ログメッセージを変更する func (s *Server) ok(c echo.Context) error { - if err := s.pool.Ping(context.Background()); err != nil { + if err := s.conn.Ping(context.Background()); err != nil { zlog.Error().Err(err).Send() return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } diff --git a/rtc_stats.go b/rtc_stats.go new file mode 100644 index 0000000..5006713 --- /dev/null +++ b/rtc_stats.go @@ -0,0 +1,86 @@ +package kohaku + +import ( + "encoding/json" + + "github.com/labstack/echo/v4" +) + +type RTCStats struct { + Timestamp float64 `json:"timestamp"` + ID string `json:"id"` + Type string `json:"type"` +} + +// type SoraRtcStats struct { +// Timestamp time.Time `json:"timestamp"` +// +// Label string +// Version string +// NodeName string +// +// Multistream bool +// Simulcast bool +// Spotlight bool +// +// Role string +// ChannelID string +// SessionID string +// ClientID string +// ConnectionID string +// +// RtcStatsTimestamp float64 +// RtcStatsType string +// RtcStatsID string +// RtcStatsData json.RawMessage +// } + +func (s *Server) rtcStats(c echo.Context, stats soraConnectionStats) error { + // TODO: Insert する場合は batch が良さそう + for _, v := range stats.Stats { + // timestamp と id と type のみを取り出す + rtcStats := new(RTCStats) + if err := json.Unmarshal(v, &rtcStats); err != nil { + return err + } + // TODO: ここで timestamp と id と type が空の場合はエラーにする + + // zlog.Debug(). + // Str("channel_id", stats.ChannelID). + // Str("connection_id", stats.ConnectionID). + // Str("type", rtcStats.Type).Send() + + // INSERT する + if err := s.conn.Exec(c.Request().Context(), ` + INSERT INTO sora_rtc_stats ( + timestamp, + version, label, node_name, + multistream, simulcast, spotlight, + role, + channel_id, session_id, client_id, connection_id, + rtc_stats_timestamp, rtc_stats_type, rtc_stats_id, + rtc_stats_data + ) VALUES ( + ?, + ?, ?, ?, + ?, ?, ?, + ?, + ?, ?, ?, ?, + ?, ?, ?, + ? + )`, + stats.Timestamp, + stats.Version, stats.Label, stats.NodeName, + stats.Multistream, stats.Simulcast, stats.Spotlight, + stats.Role, + stats.ChannelID, stats.SessionID, stats.ClientID, stats.ConnectionID, + rtcStats.Timestamp, rtcStats.Type, rtcStats.ID, + // stats は json.RawMessage なので string() で囲むだけ + string(v), + ); err != nil { + return err + } + } + + return nil +} diff --git a/server.go b/server.go index 321298b..82755e0 100644 --- a/server.go +++ b/server.go @@ -14,15 +14,13 @@ import ( "strings" "time" - "github.com/jackc/pgx/v4/pgxpool" "github.com/labstack/echo-contrib/prometheus" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" zlog "github.com/rs/zerolog/log" - db "github.com/shiguredo/kohaku/gen/sqlc" - "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" + "github.com/ClickHouse/clickhouse-go/v2" + "github.com/ClickHouse/clickhouse-go/v2/lib/driver" "github.com/go-playground/validator/v10" ) @@ -30,36 +28,68 @@ import ( type Server struct { config *Config - pool *pgxpool.Pool - query *db.Queries + conn driver.Conn echo *echo.Echo echoExporter *echo.Echo } -func NewPool(connStr string) (*pgxpool.Pool, error) { - config, err := pgxpool.ParseConfig(connStr) - if err != nil { - return nil, err - } - - pool, err := pgxpool.ConnectConfig(context.Background(), config) +func NewConnect(config *Config) (driver.Conn, error) { + addr := fmt.Sprintf("%s:%d", config.ClickHouseAddr, config.ClickHousePort) + conn, err := clickhouse.Open(&clickhouse.Options{ + Protocol: clickhouse.Native, + Addr: []string{addr}, + Auth: clickhouse.Auth{ + Database: config.ClickHouseDatabase, + Username: config.ClickHouseUsername, + Password: config.ClickHousePassword, + }, + ClientInfo: clickhouse.ClientInfo{ + Products: []struct { + Name string + Version string + }{ + {Name: "kohaku-app", Version: "0.1"}, + }, + }, + Debug: config.ClickHouseDebug, + Debugf: func(format string, v ...any) { + fmt.Printf(format+"\n", v...) + }, + // この辺りの設定は外に出すか検討 + Settings: clickhouse.Settings{ + "max_execution_time": 60, + }, + Compression: &clickhouse.Compression{ + Method: clickhouse.CompressionLZ4, + }, + DialTimeout: time.Second * 30, + MaxOpenConns: 5, + MaxIdleConns: 5, + ConnMaxLifetime: time.Duration(10) * time.Minute, + ConnOpenStrategy: clickhouse.ConnOpenInOrder, + BlockBufferSize: 10, + MaxCompressionBuffer: 10240, + // FIXME: これは開発中のみ false で実際は false にする + // TODO: オプションで外出しにするべき + // TLS: &tls.Config{ + // InsecureSkipVerify: true, + // }, + }) if err != nil { return nil, err } - - if err := pool.Ping(context.Background()); err != nil { + if err := conn.Ping(context.Background()); err != nil { + // TODO: error log return nil, err } - - return pool, nil + return conn, nil } -func NewServer(c *Config, pool *pgxpool.Pool) (*Server, error) { +func NewServer(c *Config, conn driver.Conn) (*Server, error) { s := &Server{ config: c, - pool: pool, - query: db.New(pool), + conn: conn, } if err := s.setupEchoServer(); err != nil { @@ -67,6 +97,7 @@ func NewServer(c *Config, pool *pgxpool.Pool) (*Server, error) { } zlog.Info(). + Bool("https", s.config.HTTPS). Str("addr", s.config.ListenAddr). Int("port", s.config.ListenPort). Msg("SERVER-STARTED") @@ -83,12 +114,6 @@ func NewServer(c *Config, pool *pgxpool.Pool) (*Server, error) { } func (s *Server) setupEchoServer() error { - h2s := &http2.Server{ - MaxConcurrentStreams: s.config.HTTP2MaxConcurrentStreams, - MaxReadFrameSize: s.config.HTTP2MaxReadFrameSize, - IdleTimeout: time.Duration(s.config.HTTP2IdleTimeout) * time.Second, - } - e := echo.New() // stdout にバナー出さない e.HideBanner = true @@ -101,11 +126,6 @@ func (s *Server) setupEchoServer() error { return err } - e.Server = &http.Server{ - Addr: net.JoinHostPort(s.config.ListenAddr, strconv.Itoa(s.config.ListenPort)), - Handler: h2c.NewHandler(e, h2s), - } - // クライアント認証をするかどうかのチェック if s.config.TLSVerifyCacertPath != "" { clientCAPath := s.config.TLSVerifyCacertPath @@ -122,10 +142,6 @@ func (s *Server) setupEchoServer() error { e.Server.TLSConfig = tlsConfig } - if err := http2.ConfigureServer(e.Server, h2s); err != nil { - return err - } - e.Pre(middleware.RemoveTrailingSlash()) e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ @@ -160,7 +176,6 @@ func (s *Server) setupEchoServer() error { }, })) - // e.Use(httpLogger()) e.Use(middleware.Recover()) validator := validator.New() @@ -174,9 +189,10 @@ func (s *Server) setupEchoServer() error { // ヘルスチェック e.GET("/.ok", s.ok) + e.GET("/", s.ok) - // 統計情報を突っ込むところ - e.POST("/collector", s.collector) + // 統計ウェブフックを処理する + e.POST(s.config.StatsWebhookPath, s.statsWebhook) s.echo = e @@ -197,16 +213,18 @@ func (s *Server) setupEchoExporter() { } func (s *Server) Start(ctx context.Context) error { + addr := net.JoinHostPort(s.config.ListenAddr, strconv.Itoa(s.config.ListenPort)) ch := make(chan error) go func() { defer close(ch) if s.config.HTTPS { - if err := s.echo.Server.ListenAndServeTLS(s.config.TLSFullchainFile, s.config.TLSPrivkeyFile); err != http.ErrServerClosed { + if err := s.echo.StartTLS(addr, s.config.TLSFullchainFile, s.config.TLSPrivkeyFile); err != http.ErrServerClosed { + zlog.Error().Err(err).Send() ch <- err } } else { - // HTTP/2 over TCP - if err := s.echo.Server.ListenAndServe(); err != http.ErrServerClosed { + if err := s.echo.Start(addr); err != http.ErrServerClosed { + zlog.Error().Err(err).Send() ch <- err } } @@ -227,23 +245,20 @@ func (s *Server) Start(ctx context.Context) error { } func (s *Server) StartExporter(ctx context.Context) error { + addr := net.JoinHostPort(s.config.ExporterListenAddr, strconv.Itoa(s.config.ExporterListenPort)) ch := make(chan error) go func() { var err error // exporter も HTTPS にしたい場合はこちら if s.config.ExporterHTTPS { - err = s.echoExporter.StartTLS( - net.JoinHostPort(s.config.ExporterListenAddr, strconv.Itoa(s.config.ExporterListenPort)), - s.config.TLSFullchainFile, s.config.TLSPrivkeyFile, - ) + err = s.echoExporter.StartTLS(addr, s.config.TLSFullchainFile, s.config.TLSPrivkeyFile) } else { // TODO: StartTLS 可能にする? - err = s.echoExporter.Start( - net.JoinHostPort(s.config.ExporterListenAddr, strconv.Itoa(s.config.ExporterListenPort)), - ) + err = s.echoExporter.Start(addr) } if err != nil { + zlog.Error().Err(err).Send() ch <- err } }() diff --git a/server_test.go b/server_test.go index eac12a2..b8fc94d 100644 --- a/server_test.go +++ b/server_test.go @@ -1,186 +1,172 @@ package kohaku -import ( - "context" - "crypto/tls" - "fmt" - "net" - "net/http" - "strings" - "testing" - "time" - - "golang.org/x/net/http2" - - "github.com/stretchr/testify/assert" -) - -type CertPair struct { - CertificateFile string - KeyFile string -} - -const ( - // millisecond - waitingTime = 100 -) - -var ( - url = fmt.Sprintf("https://localhost:%d/.ok", port) - - certPair = &CertPair{ - "cert/client/user.pem", - "cert/client/user.key", - } -) - -func newTestClient(nextProto string, c *CertPair) (*http.Client, error) { - var client http.Client - - // H2C クライアント - if nextProto == "h2c" { - client.Transport = &http2.Transport{ - AllowHTTP: true, - DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { - return net.Dial(network, addr) - }, - } - return &client, nil - } - - cert, err := tls.LoadX509KeyPair(c.CertificateFile, c.KeyFile) - if err != nil { - return nil, err - } - - var certs []tls.Certificate - certs = append(certs, cert) - tlsConfig := &tls.Config{ - MaxVersion: tls.VersionTLS13, - Certificates: certs, - InsecureSkipVerify: true, - NextProtos: []string{nextProto}, - } - - if nextProto == "h2" { - client.Transport = &http2.Transport{ - TLSClientConfig: tlsConfig, - } - } else { - client.Transport = &http.Transport{ - TLSClientConfig: tlsConfig, - } - } - - return &client, nil -} - -func TestMutualTLS(t *testing.T) { - s := newTestServer(config, pgPool) - go (func() { - s.Start(context.Background()) - })() - - time.Sleep(waitingTime * time.Millisecond) - - // Setup - client, err := newTestClient("http/1.1", certPair) - if err != nil { - panic(err) - } - - ctx := context.Background() - req, _ := http.NewRequestWithContext(ctx, "GET", url, strings.NewReader("")) - resp, err := client.Do(req) - if err != nil { - panic(err) - } - defer resp.Body.Close() - - assert.Equal(t, "HTTP/1.1", resp.Proto) - assert.Equal(t, http.StatusNoContent, resp.StatusCode) -} - -func TestInvalidClientCertificate(t *testing.T) { - s := newTestServer(config, pgPool) - go (func() { - s.Start(context.Background()) - })() - - time.Sleep(waitingTime * time.Millisecond) - - // Setup - invalidCertPair := &CertPair{ - "cert/client/invalid.pem", - "cert/client/invalid.key", - } - client, err := newTestClient("http/1.1", invalidCertPair) - if err != nil { - panic(err) - } - - ctx := context.Background() - req, _ := http.NewRequestWithContext(ctx, "GET", url, strings.NewReader("")) - _, err = client.Do(req) - assert.NotNil(t, err) -} - -func TestH2(t *testing.T) { - s := newTestServer(config, pgPool) - go (func() { - s.Start(context.Background()) - })() - - time.Sleep(waitingTime * time.Millisecond) - - // Setup - client, err := newTestClient("h2", certPair) - if err != nil { - panic(err) - } - - ctx := context.Background() - req, _ := http.NewRequestWithContext(ctx, "GET", url, strings.NewReader("")) - resp, err := client.Do(req) - if err != nil { - panic(err) - } - defer resp.Body.Close() - - assert.Equal(t, "HTTP/2.0", resp.Proto) - assert.Equal(t, http.StatusNoContent, resp.StatusCode) -} - -func TestH2C(t *testing.T) { - port := 25890 - config := &Config{ - HTTPS: false, - ListenAddr: "0.0.0.0", - ListenPort: port, - } - server = newTestServer(config, pgPool) - go (func() { - server.Start(context.Background()) - })() - - time.Sleep(waitingTime * time.Millisecond) - - client, err := newTestClient("h2c", nil) - if err != nil { - fmt.Print("panic") - panic(err) - } - - url = fmt.Sprintf("http://localhost:%d/.ok", port) - - ctx := context.Background() - req, _ := http.NewRequestWithContext(ctx, "GET", url, strings.NewReader("")) - resp, err := client.Do(req) - if err != nil { - panic(err) - } - defer resp.Body.Close() - - assert.Equal(t, "HTTP/2.0", resp.Proto) - assert.Equal(t, http.StatusNoContent, resp.StatusCode) -} +// type CertPair struct { +// CertificateFile string +// KeyFile string +// } +// +// const ( +// // millisecond +// waitingTime = 100 +// ) +// +// var ( +// url = fmt.Sprintf("https://localhost:%d/.ok", port) +// +// certPair = &CertPair{ +// "cert/client/user.pem", +// "cert/client/user.key", +// } +// ) +// +// func newTestClient(nextProto string, c *CertPair) (*http.Client, error) { +// var client http.Client +// +// // H2C クライアント +// if nextProto == "h2c" { +// client.Transport = &http2.Transport{ +// AllowHTTP: true, +// DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { +// return net.Dial(network, addr) +// }, +// } +// return &client, nil +// } +// +// cert, err := tls.LoadX509KeyPair(c.CertificateFile, c.KeyFile) +// if err != nil { +// return nil, err +// } +// +// var certs []tls.Certificate +// certs = append(certs, cert) +// tlsConfig := &tls.Config{ +// MaxVersion: tls.VersionTLS13, +// Certificates: certs, +// InsecureSkipVerify: true, +// NextProtos: []string{nextProto}, +// } +// +// if nextProto == "h2" { +// client.Transport = &http2.Transport{ +// TLSClientConfig: tlsConfig, +// } +// } else { +// client.Transport = &http.Transport{ +// TLSClientConfig: tlsConfig, +// } +// } +// +// return &client, nil +// } +// +// func TestMutualTLS(t *testing.T) { +// s := newTestServer(config, conn) +// go (func() { +// s.Start(context.Background()) +// })() +// +// time.Sleep(waitingTime * time.Millisecond) +// +// // Setup +// client, err := newTestClient("http/1.1", certPair) +// if err != nil { +// panic(err) +// } +// +// ctx := context.Background() +// req, _ := http.NewRequestWithContext(ctx, "GET", url, strings.NewReader("")) +// resp, err := client.Do(req) +// if err != nil { +// panic(err) +// } +// defer resp.Body.Close() +// +// assert.Equal(t, "HTTP/1.1", resp.Proto) +// assert.Equal(t, http.StatusNoContent, resp.StatusCode) +// } +// +// func TestInvalidClientCertificate(t *testing.T) { +// s := newTestServer(config, conn) +// go (func() { +// s.Start(context.Background()) +// })() +// +// time.Sleep(waitingTime * time.Millisecond) +// +// // Setup +// invalidCertPair := &CertPair{ +// "cert/client/invalid.pem", +// "cert/client/invalid.key", +// } +// client, err := newTestClient("http/1.1", invalidCertPair) +// if err != nil { +// panic(err) +// } +// +// ctx := context.Background() +// req, _ := http.NewRequestWithContext(ctx, "GET", url, strings.NewReader("")) +// _, err = client.Do(req) +// assert.NotNil(t, err) +// } +// +// func TestH2(t *testing.T) { +// s := newTestServer(config, conn) +// go (func() { +// s.Start(context.Background()) +// })() +// +// time.Sleep(waitingTime * time.Millisecond) +// +// // Setup +// client, err := newTestClient("h2", certPair) +// if err != nil { +// panic(err) +// } +// +// ctx := context.Background() +// req, _ := http.NewRequestWithContext(ctx, "GET", url, strings.NewReader("")) +// resp, err := client.Do(req) +// if err != nil { +// panic(err) +// } +// defer resp.Body.Close() +// +// assert.Equal(t, "HTTP/2.0", resp.Proto) +// assert.Equal(t, http.StatusNoContent, resp.StatusCode) +// } +// +// func TestH2C(t *testing.T) { +// port := 25890 +// config := &Config{ +// HTTPS: false, +// ListenAddr: "0.0.0.0", +// ListenPort: port, +// } +// server = newTestServer(config, conn) +// go (func() { +// server.Start(context.Background()) +// })() +// +// time.Sleep(waitingTime * time.Millisecond) +// +// client, err := newTestClient("h2c", nil) +// if err != nil { +// fmt.Print("panic") +// panic(err) +// } +// +// url = fmt.Sprintf("http://localhost:%d/.ok", port) +// +// ctx := context.Background() +// req, _ := http.NewRequestWithContext(ctx, "GET", url, strings.NewReader("")) +// resp, err := client.Do(req) +// if err != nil { +// panic(err) +// } +// defer resp.Body.Close() +// +// assert.Equal(t, "HTTP/2.0", resp.Proto) +// assert.Equal(t, http.StatusNoContent, resp.StatusCode) +// } +// diff --git a/sora.go b/sora.go index ae958a9..8bf239c 100644 --- a/sora.go +++ b/sora.go @@ -21,7 +21,7 @@ type soraStats struct { NodeName string `json:"node_name" validate:"required"` } -// type: connection.user-agent / type: connection.sora +// type: connection.rtc / type: connection.sora type soraConnectionStats struct { soraStats diff --git a/collector_h.go b/sora_stats_webhook.go similarity index 64% rename from collector_h.go rename to sora_stats_webhook.go index 2105024..f3204b1 100644 --- a/collector_h.go +++ b/sora_stats_webhook.go @@ -8,19 +8,10 @@ import ( ) // ログレベル、ログメッセージを変更する -func (s *Server) collector(c echo.Context) error { - if c.Request().ProtoMajor != 2 { - zlog.Error(). - Str("Proto", c.Request().Proto). - Int("ProtoMajor", c.Request().ProtoMajor). - Int("ProtoMinor", c.Request().ProtoMinor). - Msg("PROTOCOL-VIOLATION") - return echo.NewHTTPError(http.StatusBadRequest) - } - - t := c.Request().Header.Get("x-sora-stats-exporter-type") +func (s *Server) statsWebhook(c echo.Context) error { + t := c.Request().Header.Get("sora-stats-webhook-type") switch t { - case "connection.user-agent": + case "connection.rtc": stats := new(soraConnectionStats) if err := c.Bind(stats); err != nil { zlog.Warn().Err(err).Str("type", t).Send() @@ -30,7 +21,7 @@ func (s *Server) collector(c echo.Context) error { zlog.Warn().Err(err).Bool("simulcast", *stats.Simulcast).Str("type", t).Send() return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } - if err := s.collectorUserAgentStats(c, *stats); err != nil { + if err := s.rtcStats(c, *stats); err != nil { zlog.Warn().Err(err).Str("type", t).Send() return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } diff --git a/sqlc.yaml b/sqlc.yaml deleted file mode 100644 index 5387806..0000000 --- a/sqlc.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# https://docs.sqlc.dev/en/latest/reference/config.html -version: "1" -packages: - - name: "db" - path: "./gen/sqlc" - queries: "db/query.sql" - schema: "db/schema.sql" - engine: "postgresql" - sql_package: "pgx/v4" - emit_exact_table_names: true - emit_empty_slices: true - emit_exported_queries: true - emit_json_tags: true - emit_interface: false - emit_prepared_queries: false - emit_pointers_for_null_types: true - query_parameter_limit: 0 - # emit_result_struct_pointers: true - # emit_params_struct_pointers: true diff --git a/user_agent_stats.go b/user_agent_stats.go deleted file mode 100644 index 3f56acf..0000000 --- a/user_agent_stats.go +++ /dev/null @@ -1,68 +0,0 @@ -package kohaku - -import ( - "encoding/json" - - "github.com/jackc/pgtype" - "github.com/labstack/echo/v4" - db "github.com/shiguredo/kohaku/gen/sqlc" -) - -type RTCStats struct { - Timestamp float64 `json:"timestamp"` - ID string `json:"id"` - Type string `json:"type"` -} - -func (s *Server) collectorUserAgentStats(c echo.Context, stats soraConnectionStats) error { - if err := s.query.InsertSoraConnection(c.Request().Context(), db.InsertSoraConnectionParams{ - Timestamp: stats.Timestamp, - Label: stats.Label, - Version: stats.Version, - NodeName: stats.NodeName, - Multistream: *stats.Multistream, - Simulcast: *stats.Simulcast, - Spotlight: *stats.Spotlight, - Role: db.SoraConnectionRole(stats.Role), - ChannelID: stats.ChannelID, - SessionID: stats.SessionID, - ClientID: stats.ClientID, - ConnectionID: stats.ConnectionID, - }); err != nil { - return err - } - - for _, v := range stats.Stats { - // ここで data をいれる JSONB を用意する - var jsonb pgtype.JSONB - if err := jsonb.Set(v); err != nil { - return err - } - - // timestamp と id と type のみを取り出す - rtcStats := new(RTCStats) - if err := json.Unmarshal(v, &rtcStats); err != nil { - return err - } - // TODO: ここで timestamp と id と type が空の場合はエラーにする - - /* - 保存する、ただし channel_id と connection_id と rtc_stats_id と rtc_stats_type が同一の場合、 - rtc_stats_timestamp 以外が変更されていた場合のみ更新する - */ - if err := s.query.InsertSoraUserAgentStats(c.Request().Context(), db.InsertSoraUserAgentStatsParams{ - Timestamp: stats.Timestamp, - ChannelID: stats.ChannelID, - ConnectionID: stats.ConnectionID, - RtcStatsTimestamp: rtcStats.Timestamp, - RtcStatsType: rtcStats.Type, - RtcStatsID: rtcStats.ID, - RtcStatsData: jsonb, - }); err != nil { - return err - } - - } - - return nil -}