Merge pull request #780 from shocknet/metrics-fix

Metrics fix
This commit is contained in:
Justin (shocknet) 2025-01-17 14:59:55 -05:00 committed by GitHub
commit aa47bbe7c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 270 additions and 23 deletions

View file

@ -158,9 +158,14 @@ The nostr server will send back a message response, and inside the body there wi
- This methods has an __empty__ __request__ body - This methods has an __empty__ __request__ body
- output: [LndSeed](#LndSeed) - output: [LndSeed](#LndSeed)
- GetSingleUsageMetrics
- auth type: __Metrics__
- input: [SingleUsageMetricReq](#SingleUsageMetricReq)
- output: [UsageMetricTlv](#UsageMetricTlv)
- GetUsageMetrics - GetUsageMetrics
- auth type: __Metrics__ - auth type: __Metrics__
- This methods has an __empty__ __request__ body - input: [LatestUsageMetricReq](#LatestUsageMetricReq)
- output: [UsageMetrics](#UsageMetrics) - output: [UsageMetrics](#UsageMetrics)
- GetUserInfo - GetUserInfo
@ -581,11 +586,18 @@ The nostr server will send back a message response, and inside the body there wi
- This methods has an __empty__ __request__ body - This methods has an __empty__ __request__ body
- output: [LndSeed](#LndSeed) - output: [LndSeed](#LndSeed)
- GetSingleUsageMetrics
- auth type: __Metrics__
- http method: __post__
- http route: __/api/reports/usage/single__
- input: [SingleUsageMetricReq](#SingleUsageMetricReq)
- output: [UsageMetricTlv](#UsageMetricTlv)
- GetUsageMetrics - GetUsageMetrics
- auth type: __Metrics__ - auth type: __Metrics__
- http method: __post__ - http method: __post__
- http route: __/api/reports/usage__ - http route: __/api/reports/usage__
- This methods has an __empty__ __request__ body - input: [LatestUsageMetricReq](#LatestUsageMetricReq)
- output: [UsageMetrics](#UsageMetrics) - output: [UsageMetrics](#UsageMetrics)
- GetUserInfo - GetUserInfo
@ -1070,6 +1082,9 @@ The nostr server will send back a message response, and inside the body there wi
- __token__: _string_ - __token__: _string_
- __url__: _string_ - __url__: _string_
### LatestUsageMetricReq
- __limit__: _number_ *this field is optional
### LinkNPubThroughTokenRequest ### LinkNPubThroughTokenRequest
- __token__: _string_ - __token__: _string_
@ -1140,6 +1155,8 @@ The nostr server will send back a message response, and inside the body there wi
- __payLink__: _string_ - __payLink__: _string_
- __tag__: _string_ - __tag__: _string_
### MetricsFile
### MigrationUpdate ### MigrationUpdate
- __closure__: _[ClosureMigration](#ClosureMigration)_ *this field is optional - __closure__: _[ClosureMigration](#ClosureMigration)_ *this field is optional
- __relays__: _[RelaysMigration](#RelaysMigration)_ *this field is optional - __relays__: _[RelaysMigration](#RelaysMigration)_ *this field is optional
@ -1297,6 +1314,11 @@ The nostr server will send back a message response, and inside the body there wi
- __amount__: _number_ - __amount__: _number_
- __invoice__: _string_ - __invoice__: _string_
### SingleUsageMetricReq
- __app_id__: _string_
- __metrics_name__: _string_
- __page__: _number_
### UpdateChannelPolicyRequest ### UpdateChannelPolicyRequest
- __policy__: _[ChannelPolicy](#ChannelPolicy)_ - __policy__: _[ChannelPolicy](#ChannelPolicy)_
- __update__: _[UpdateChannelPolicyRequest_update](#UpdateChannelPolicyRequest_update)_ - __update__: _[UpdateChannelPolicyRequest_update](#UpdateChannelPolicyRequest_update)_

View file

@ -93,7 +93,8 @@ type Client struct {
GetNPubLinkingState func(req GetNPubLinking) (*NPubLinking, error) GetNPubLinkingState func(req GetNPubLinking) (*NPubLinking, error)
GetPaymentState func(req GetPaymentStateRequest) (*PaymentState, error) GetPaymentState func(req GetPaymentStateRequest) (*PaymentState, error)
GetSeed func() (*LndSeed, error) GetSeed func() (*LndSeed, error)
GetUsageMetrics func() (*UsageMetrics, error) GetSingleUsageMetrics func(req SingleUsageMetricReq) (*UsageMetricTlv, error)
GetUsageMetrics func(req LatestUsageMetricReq) (*UsageMetrics, error)
GetUserInfo func() (*UserInfo, error) GetUserInfo func() (*UserInfo, error)
GetUserOffer func(req OfferId) (*OfferConfig, error) GetUserOffer func(req OfferId) (*OfferConfig, error)
GetUserOfferInvoices func(req GetUserOfferInvoicesReq) (*OfferInvoices, error) GetUserOfferInvoices func(req GetUserOfferInvoicesReq) (*OfferInvoices, error)
@ -1057,13 +1058,45 @@ func NewClient(params ClientParams) *Client {
} }
return &res, nil return &res, nil
}, },
GetUsageMetrics: func() (*UsageMetrics, error) { GetSingleUsageMetrics: func(req SingleUsageMetricReq) (*UsageMetricTlv, error) {
auth, err := params.RetrieveMetricsAuth()
if err != nil {
return nil, err
}
finalRoute := "/api/reports/usage/single"
body, err := json.Marshal(req)
if err != nil {
return nil, err
}
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
if err != nil {
return nil, err
}
result := ResultError{}
err = json.Unmarshal(resBody, &result)
if err != nil {
return nil, err
}
if result.Status == "ERROR" {
return nil, fmt.Errorf(result.Reason)
}
res := UsageMetricTlv{}
err = json.Unmarshal(resBody, &res)
if err != nil {
return nil, err
}
return &res, nil
},
GetUsageMetrics: func(req LatestUsageMetricReq) (*UsageMetrics, error) {
auth, err := params.RetrieveMetricsAuth() auth, err := params.RetrieveMetricsAuth()
if err != nil { if err != nil {
return nil, err return nil, err
} }
finalRoute := "/api/reports/usage" finalRoute := "/api/reports/usage"
body := []byte{} body, err := json.Marshal(req)
if err != nil {
return nil, err
}
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth) resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -322,6 +322,9 @@ type HttpCreds struct {
Token string `json:"token"` Token string `json:"token"`
Url string `json:"url"` Url string `json:"url"`
} }
type LatestUsageMetricReq struct {
Limit int64 `json:"limit"`
}
type LinkNPubThroughTokenRequest struct { type LinkNPubThroughTokenRequest struct {
Token string `json:"token"` Token string `json:"token"`
} }
@ -392,6 +395,8 @@ type LnurlWithdrawInfoResponse struct {
Paylink string `json:"payLink"` Paylink string `json:"payLink"`
Tag string `json:"tag"` Tag string `json:"tag"`
} }
type MetricsFile struct {
}
type MigrationUpdate struct { type MigrationUpdate struct {
Closure *ClosureMigration `json:"closure"` Closure *ClosureMigration `json:"closure"`
Relays *RelaysMigration `json:"relays"` Relays *RelaysMigration `json:"relays"`
@ -549,6 +554,11 @@ type SetMockInvoiceAsPaidRequest struct {
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
Invoice string `json:"invoice"` Invoice string `json:"invoice"`
} }
type SingleUsageMetricReq struct {
App_id string `json:"app_id"`
Metrics_name string `json:"metrics_name"`
Page int64 `json:"page"`
}
type UpdateChannelPolicyRequest struct { type UpdateChannelPolicyRequest struct {
Policy *ChannelPolicy `json:"policy"` Policy *ChannelPolicy `json:"policy"`
Update *UpdateChannelPolicyRequest_update `json:"update"` Update *UpdateChannelPolicyRequest_update `json:"update"`

View file

@ -1106,6 +1106,28 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }]) opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e } } catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
}) })
if (!opts.allowNotImplementedMethods && !methods.GetSingleUsageMetrics) throw new Error('method: GetSingleUsageMetrics is not implemented')
app.post('/api/reports/usage/single', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'GetSingleUsageMetrics', batch: false, nostr: false, batchSize: 0}
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
let authCtx: Types.AuthContext = {}
try {
if (!methods.GetSingleUsageMetrics) throw new Error('method: GetSingleUsageMetrics is not implemented')
const authContext = await opts.MetricsAuthGuard(req.headers['authorization'])
authCtx = authContext
stats.guard = process.hrtime.bigint()
const request = req.body
const error = Types.SingleUsageMetricReqValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
const query = req.query
const params = req.params
const response = await methods.GetSingleUsageMetrics({rpcName:'GetSingleUsageMetrics', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res.json({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetUsageMetrics) throw new Error('method: GetUsageMetrics is not implemented') if (!opts.allowNotImplementedMethods && !methods.GetUsageMetrics) throw new Error('method: GetUsageMetrics is not implemented')
app.post('/api/reports/usage', async (req, res) => { app.post('/api/reports/usage', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'GetUsageMetrics', batch: false, nostr: false, batchSize: 0} const info: Types.RequestInfo = { rpcName: 'GetUsageMetrics', batch: false, nostr: false, batchSize: 0}
@ -1116,10 +1138,13 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const authContext = await opts.MetricsAuthGuard(req.headers['authorization']) const authContext = await opts.MetricsAuthGuard(req.headers['authorization'])
authCtx = authContext authCtx = authContext
stats.guard = process.hrtime.bigint() stats.guard = process.hrtime.bigint()
stats.validate = stats.guard const request = req.body
const error = Types.LatestUsageMetricReqValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
const query = req.query const query = req.query
const params = req.params const params = req.params
const response = await methods.GetUsageMetrics({rpcName:'GetUsageMetrics', ctx:authContext }) const response = await methods.GetUsageMetrics({rpcName:'GetUsageMetrics', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint() stats.handle = process.hrtime.bigint()
res.json({status: 'OK', ...response}) res.json({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }]) opts.metricsCallback([{ ...info, ...stats, ...authContext }])

View file

@ -494,11 +494,25 @@ export default (params: ClientParams) => ({
} }
return { status: 'ERROR', reason: 'invalid response' } return { status: 'ERROR', reason: 'invalid response' }
}, },
GetUsageMetrics: async (): Promise<ResultError | ({ status: 'OK' }& Types.UsageMetrics)> => { GetSingleUsageMetrics: async (request: Types.SingleUsageMetricReq): Promise<ResultError | ({ status: 'OK' }& Types.UsageMetricTlv)> => {
const auth = await params.retrieveMetricsAuth()
if (auth === null) throw new Error('retrieveMetricsAuth() returned null')
let finalRoute = '/api/reports/usage/single'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.UsageMetricTlvValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetUsageMetrics: async (request: Types.LatestUsageMetricReq): Promise<ResultError | ({ status: 'OK' }& Types.UsageMetrics)> => {
const auth = await params.retrieveMetricsAuth() const auth = await params.retrieveMetricsAuth()
if (auth === null) throw new Error('retrieveMetricsAuth() returned null') if (auth === null) throw new Error('retrieveMetricsAuth() returned null')
let finalRoute = '/api/reports/usage' let finalRoute = '/api/reports/usage'
const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } }) const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') { if (data.status === 'OK') {
const result = data const result = data

View file

@ -422,10 +422,26 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
} }
return { status: 'ERROR', reason: 'invalid response' } return { status: 'ERROR', reason: 'invalid response' }
}, },
GetUsageMetrics: async (): Promise<ResultError | ({ status: 'OK' }& Types.UsageMetrics)> => { GetSingleUsageMetrics: async (request: Types.SingleUsageMetricReq): Promise<ResultError | ({ status: 'OK' }& Types.UsageMetricTlv)> => {
const auth = await params.retrieveNostrMetricsAuth() const auth = await params.retrieveNostrMetricsAuth()
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null') if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
const nostrRequest: NostrRequest = {} const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'GetSingleUsageMetrics',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.UsageMetricTlvValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetUsageMetrics: async (request: Types.LatestUsageMetricReq): Promise<ResultError | ({ status: 'OK' }& Types.UsageMetrics)> => {
const auth = await params.retrieveNostrMetricsAuth()
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'GetUsageMetrics',authIdentifier:auth, ...nostrRequest }) const data = await send(params.pubDestination, {rpcName:'GetUsageMetrics',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') { if (data.status === 'OK') {

View file

@ -799,14 +799,33 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }]) opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e } }catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break break
case 'GetSingleUsageMetrics':
try {
if (!methods.GetSingleUsageMetrics) throw new Error('method: GetSingleUsageMetrics is not implemented')
const authContext = await opts.NostrMetricsAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.SingleUsageMetricReqValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
const response = await methods.GetSingleUsageMetrics({rpcName:'GetSingleUsageMetrics', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'GetUsageMetrics': case 'GetUsageMetrics':
try { try {
if (!methods.GetUsageMetrics) throw new Error('method: GetUsageMetrics is not implemented') if (!methods.GetUsageMetrics) throw new Error('method: GetUsageMetrics is not implemented')
const authContext = await opts.NostrMetricsAuthGuard(req.appId, req.authIdentifier) const authContext = await opts.NostrMetricsAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint() stats.guard = process.hrtime.bigint()
authCtx = authContext authCtx = authContext
stats.validate = stats.guard const request = req.body
const response = await methods.GetUsageMetrics({rpcName:'GetUsageMetrics', ctx:authContext }) const error = Types.LatestUsageMetricReqValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
const response = await methods.GetUsageMetrics({rpcName:'GetUsageMetrics', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint() stats.handle = process.hrtime.bigint()
res({status: 'OK', ...response}) res({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }]) opts.metricsCallback([{ ...info, ...stats, ...authContext }])

View file

@ -27,8 +27,8 @@ export type GuestWithPubMethodOutputs = LinkNPubThroughToken_Output | UseInviteL
export type MetricsContext = { export type MetricsContext = {
operator_id: string operator_id: string
} }
export type MetricsMethodInputs = GetAppsMetrics_Input | GetErrorStats_Input | GetLndMetrics_Input | GetUsageMetrics_Input export type MetricsMethodInputs = GetAppsMetrics_Input | GetErrorStats_Input | GetLndMetrics_Input | GetSingleUsageMetrics_Input | GetUsageMetrics_Input
export type MetricsMethodOutputs = GetAppsMetrics_Output | GetErrorStats_Output | GetLndMetrics_Output | GetUsageMetrics_Output export type MetricsMethodOutputs = GetAppsMetrics_Output | GetErrorStats_Output | GetLndMetrics_Output | GetSingleUsageMetrics_Output | GetUsageMetrics_Output
export type UserContext = { export type UserContext = {
app_id: string app_id: string
app_user_id: string app_user_id: string
@ -161,7 +161,10 @@ export type GetPaymentState_Output = ResultError | ({ status: 'OK' } & PaymentSt
export type GetSeed_Input = {rpcName:'GetSeed'} export type GetSeed_Input = {rpcName:'GetSeed'}
export type GetSeed_Output = ResultError | ({ status: 'OK' } & LndSeed) export type GetSeed_Output = ResultError | ({ status: 'OK' } & LndSeed)
export type GetUsageMetrics_Input = {rpcName:'GetUsageMetrics'} export type GetSingleUsageMetrics_Input = {rpcName:'GetSingleUsageMetrics', req: SingleUsageMetricReq}
export type GetSingleUsageMetrics_Output = ResultError | ({ status: 'OK' } & UsageMetricTlv)
export type GetUsageMetrics_Input = {rpcName:'GetUsageMetrics', req: LatestUsageMetricReq}
export type GetUsageMetrics_Output = ResultError | ({ status: 'OK' } & UsageMetrics) export type GetUsageMetrics_Output = ResultError | ({ status: 'OK' } & UsageMetrics)
export type GetUserInfo_Input = {rpcName:'GetUserInfo'} export type GetUserInfo_Input = {rpcName:'GetUserInfo'}
@ -318,6 +321,7 @@ export type ServerMethods = {
GetNPubLinkingState?: (req: GetNPubLinkingState_Input & {ctx: AppContext }) => Promise<NPubLinking> GetNPubLinkingState?: (req: GetNPubLinkingState_Input & {ctx: AppContext }) => Promise<NPubLinking>
GetPaymentState?: (req: GetPaymentState_Input & {ctx: UserContext }) => Promise<PaymentState> GetPaymentState?: (req: GetPaymentState_Input & {ctx: UserContext }) => Promise<PaymentState>
GetSeed?: (req: GetSeed_Input & {ctx: AdminContext }) => Promise<LndSeed> GetSeed?: (req: GetSeed_Input & {ctx: AdminContext }) => Promise<LndSeed>
GetSingleUsageMetrics?: (req: GetSingleUsageMetrics_Input & {ctx: MetricsContext }) => Promise<UsageMetricTlv>
GetUsageMetrics?: (req: GetUsageMetrics_Input & {ctx: MetricsContext }) => Promise<UsageMetrics> GetUsageMetrics?: (req: GetUsageMetrics_Input & {ctx: MetricsContext }) => Promise<UsageMetrics>
GetUserInfo?: (req: GetUserInfo_Input & {ctx: UserContext }) => Promise<UserInfo> GetUserInfo?: (req: GetUserInfo_Input & {ctx: UserContext }) => Promise<UserInfo>
GetUserOffer?: (req: GetUserOffer_Input & {ctx: UserContext }) => Promise<OfferConfig> GetUserOffer?: (req: GetUserOffer_Input & {ctx: UserContext }) => Promise<OfferConfig>
@ -1799,6 +1803,25 @@ export const HttpCredsValidate = (o?: HttpCreds, opts: HttpCredsOptions = {}, pa
return null return null
} }
export type LatestUsageMetricReq = {
limit?: number
}
export type LatestUsageMetricReqOptionalField = 'limit'
export const LatestUsageMetricReqOptionalFields: LatestUsageMetricReqOptionalField[] = ['limit']
export type LatestUsageMetricReqOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: LatestUsageMetricReqOptionalField[]
limit_CustomCheck?: (v?: number) => boolean
}
export const LatestUsageMetricReqValidate = (o?: LatestUsageMetricReq, opts: LatestUsageMetricReqOptions = {}, path: string = 'LatestUsageMetricReq::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if ((o.limit || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('limit')) && typeof o.limit !== 'number') return new Error(`${path}.limit: is not a number`)
if (opts.limit_CustomCheck && !opts.limit_CustomCheck(o.limit)) return new Error(`${path}.limit: custom check failed`)
return null
}
export type LinkNPubThroughTokenRequest = { export type LinkNPubThroughTokenRequest = {
token: string token: string
} }
@ -2234,6 +2257,19 @@ export const LnurlWithdrawInfoResponseValidate = (o?: LnurlWithdrawInfoResponse,
return null return null
} }
export type MetricsFile = {
}
export const MetricsFileOptionalFields: [] = []
export type MetricsFileOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
}
export const MetricsFileValidate = (o?: MetricsFile, opts: MetricsFileOptions = {}, path: string = 'MetricsFile::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
return null
}
export type MigrationUpdate = { export type MigrationUpdate = {
closure?: ClosureMigration closure?: ClosureMigration
relays?: RelaysMigration relays?: RelaysMigration
@ -3142,6 +3178,34 @@ export const SetMockInvoiceAsPaidRequestValidate = (o?: SetMockInvoiceAsPaidRequ
return null return null
} }
export type SingleUsageMetricReq = {
app_id: string
metrics_name: string
page: number
}
export const SingleUsageMetricReqOptionalFields: [] = []
export type SingleUsageMetricReqOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
app_id_CustomCheck?: (v: string) => boolean
metrics_name_CustomCheck?: (v: string) => boolean
page_CustomCheck?: (v: number) => boolean
}
export const SingleUsageMetricReqValidate = (o?: SingleUsageMetricReq, opts: SingleUsageMetricReqOptions = {}, path: string = 'SingleUsageMetricReq::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.app_id !== 'string') return new Error(`${path}.app_id: is not a string`)
if (opts.app_id_CustomCheck && !opts.app_id_CustomCheck(o.app_id)) return new Error(`${path}.app_id: custom check failed`)
if (typeof o.metrics_name !== 'string') return new Error(`${path}.metrics_name: is not a string`)
if (opts.metrics_name_CustomCheck && !opts.metrics_name_CustomCheck(o.metrics_name)) return new Error(`${path}.metrics_name: custom check failed`)
if (typeof o.page !== 'number') return new Error(`${path}.page: is not a number`)
if (opts.page_CustomCheck && !opts.page_CustomCheck(o.page)) return new Error(`${path}.page: custom check failed`)
return null
}
export type UpdateChannelPolicyRequest = { export type UpdateChannelPolicyRequest = {
policy: ChannelPolicy policy: ChannelPolicy
update: UpdateChannelPolicyRequest_update update: UpdateChannelPolicyRequest_update

View file

@ -172,12 +172,18 @@ service LightningPub {
option (nostr) = true; option (nostr) = true;
} }
rpc GetUsageMetrics(structs.Empty) returns (structs.UsageMetrics) { rpc GetUsageMetrics(structs.LatestUsageMetricReq) returns (structs.UsageMetrics) {
option (auth_type) = "Metrics"; option (auth_type) = "Metrics";
option (http_method) = "post"; option (http_method) = "post";
option (http_route) = "/api/reports/usage"; option (http_route) = "/api/reports/usage";
option (nostr) = true; option (nostr) = true;
} }
rpc GetSingleUsageMetrics(structs.SingleUsageMetricReq) returns (structs.UsageMetricTlv) {
option (auth_type) = "Metrics";
option (http_method) = "post";
option (http_route) = "/api/reports/usage/single";
option (nostr) = true;
}
rpc GetErrorStats(structs.Empty) returns (structs.ErrorStats) { rpc GetErrorStats(structs.Empty) returns (structs.ErrorStats) {
option (auth_type) = "Metrics"; option (auth_type) = "Metrics";
option (http_method) = "post"; option (http_method) = "post";

View file

@ -33,6 +33,20 @@ message ErrorStats {
ErrorStat past1m = 5; ErrorStat past1m = 5;
} }
message MetricsFile {
}
message LatestUsageMetricReq {
optional int64 limit = 1;
}
message SingleUsageMetricReq {
string app_id = 1;
string metrics_name = 2;
int64 page = 3;
}
message UsageMetric { message UsageMetric {
int64 processed_at_ms = 1; int64 processed_at_ms = 1;
int64 parsed_in_nano = 2; int64 parsed_in_nano = 2;

View file

@ -690,7 +690,7 @@ export default class {
throw new Error("user is banned, cannot retrieve operations") throw new Error("user is banned, cannot retrieve operations")
} }
const [outgoingInvoices, outgoingTransactions, incomingInvoices, incomingTransactions, incomingUserToUser, outgoingUserToUser] = await Promise.all([ const [outgoingInvoices, outgoingTransactions, incomingInvoices, incomingTransactions, incomingUserToUser, outgoingUserToUser] = await Promise.all([
this.storage.paymentStorage.GetUserInvoicePayments(userId, req.latestOutgoingInvoice, req.max_size), this.storage.paymentStorage.GetUserInvoicePayments(userId, req.latestOutgoingInvoice, req.max_size), //
this.storage.paymentStorage.GetUserTransactionPayments(userId, req.latestOutgoingTx, req.max_size), this.storage.paymentStorage.GetUserTransactionPayments(userId, req.latestOutgoingTx, req.max_size),
this.storage.paymentStorage.GetUserInvoicesFlaggedAsPaid(userId, req.latestIncomingInvoice, req.max_size), this.storage.paymentStorage.GetUserInvoicesFlaggedAsPaid(userId, req.latestIncomingInvoice, req.max_size),
this.storage.paymentStorage.GetUserReceivingTransactions(userId, req.latestIncomingTx, req.max_size), this.storage.paymentStorage.GetUserReceivingTransactions(userId, req.latestIncomingTx, req.max_size),

View file

@ -69,8 +69,12 @@ export default class Handler {
})) }))
} }
async GetUsageMetrics(): Promise<Types.UsageMetrics> { async GetUsageMetrics(req: Types.LatestUsageMetricReq): Promise<Types.UsageMetrics> {
return this.storage.metricsEventStorage.LoadLatestMetrics() return this.storage.metricsEventStorage.LoadLatestMetrics(req.limit)
}
async GetSingleUsageMetrics(req: Types.SingleUsageMetricReq): Promise<Types.UsageMetricTlv> {
return this.storage.metricsEventStorage.LoadMetricsFile(req.app_id, req.metrics_name, req.page)
} }
async GetErrorStats(): Promise<Types.ErrorStats> { async GetErrorStats(): Promise<Types.ErrorStats> {

View file

@ -4,8 +4,11 @@ import main from '../main/index.js'
import Main from '../main/index.js' import Main from '../main/index.js'
export default (mainHandler: Main): Types.ServerMethods => { export default (mainHandler: Main): Types.ServerMethods => {
return { return {
GetUsageMetrics: async ({ ctx }) => { GetUsageMetrics: async ({ ctx, req }) => {
return mainHandler.metricsManager.GetUsageMetrics() return mainHandler.metricsManager.GetUsageMetrics(req)
},
GetSingleUsageMetrics: async ({ ctx, req }) => {
return mainHandler.metricsManager.GetSingleUsageMetrics(req)
}, },
GetErrorStats: async ({ ctx }) => { GetErrorStats: async ({ ctx }) => {
return mainHandler.metricsManager.GetErrorStats() return mainHandler.metricsManager.GetErrorStats()

View file

@ -107,7 +107,7 @@ export default class {
} }
LoadLatestMetrics = async (): Promise<Types.UsageMetrics> => { LoadLatestMetrics = async (limit = 100): Promise<Types.UsageMetrics> => {
this.persistMetrics() this.persistMetrics()
const metrics: Types.UsageMetrics = { apps: {} } const metrics: Types.UsageMetrics = { apps: {} }
this.foreachMetricMethodFile((app, method, tlvFiles) => { this.foreachMetricMethodFile((app, method, tlvFiles) => {
@ -120,6 +120,9 @@ export default class {
if (!metrics.apps[app]) { if (!metrics.apps[app]) {
metrics.apps[app] = { app_metrics: {} } metrics.apps[app] = { app_metrics: {} }
} }
if (decoded.length > limit) {
decoded.splice(0, decoded.length - limit)
}
metrics.apps[app].app_metrics[method] = { metrics.apps[app].app_metrics[method] = {
base_64_tlvs: decoded.map(d => Buffer.from(d).toString('base64')), base_64_tlvs: decoded.map(d => Buffer.from(d).toString('base64')),
current_chunk: latest, current_chunk: latest,
@ -129,6 +132,20 @@ export default class {
return metrics return metrics
} }
LoadMetricsFile = async (app: string, method: string, chunk: number): Promise<Types.UsageMetricTlv> => {
if (!this.metaReady || !this.metricsMeta[app] || !this.metricsMeta[app][method] || !this.metricsMeta[app][method].chunks.includes(chunk)) {
throw new Error("metrics not found")
}
const fullPath = [this.metricsPath, app, method, `${chunk}.mtlv`].join("/")
const tlv = fs.readFileSync(fullPath)
const decoded = decodeListTLV(parseTLV(tlv))
return {
base_64_tlvs: decoded.map(d => Buffer.from(d).toString('base64')),
current_chunk: chunk,
available_chunks: this.metricsMeta[app][method].chunks
}
}
persistMetrics = () => { persistMetrics = () => {
if (!this.metaReady) { if (!this.metaReady) {
throw new Error("meta metrics not ready") throw new Error("meta metrics not ready")

View file

@ -213,7 +213,7 @@ export default class {
user_id: userId user_id: userId
}, },
serial_id: MoreThanOrEqual(fromIndex), serial_id: MoreThanOrEqual(fromIndex),
paid_at_unix: MoreThan(0), paid_at_unix: MoreThan(-1),
}, },
order: { order: {
paid_at_unix: 'DESC' paid_at_unix: 'DESC'