Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[faucet] Add custom metrics #1143

Merged
merged 1 commit into from
Oct 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/faucet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"cli": "ts-node scripts/cli.ts"
},
"dependencies": {
"@google-cloud/logging": "^5.3.1",
"debug": "^4.1.1",
"eth-lib": "^0.2.8",
"firebase": "^6.2.2",
Expand All @@ -40,6 +41,5 @@
"firebase-tools": "^7.3.0",
"rimraf": "2.6.3",
"yargs": "14.0.0"

}
}
8 changes: 2 additions & 6 deletions packages/faucet/src/celo-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import getStableTokenInstance from './contracts/StableToken'
import { Escrow } from './contracts/types/Escrow'
import { GoldToken } from './contracts/types/GoldToken'
import { StableToken } from './contracts/types/StableToken'
import { injectDebugProvider } from './debug-provider'
import { getAddress, sendTx } from './tx'

export class CeloAdapter {
Expand All @@ -22,7 +21,8 @@ export class CeloAdapter {
private readonly escrowAddress: string,
private readonly goldTokenAddress: string
) {
injectDebugProvider(web3)
// Uncomment when in need for debug
// injectDebugProvider(web3)

this.privateKey = this.web3.utils.isHexStrict(pk) ? pk : '0x' + pk
this.defaultAddress = getAddress(this.web3, this.privateKey)
Expand All @@ -31,10 +31,6 @@ export class CeloAdapter {
this.escrow = getEscrowInstance(this.web3, escrowAddress)
}

getStableToken() {
return
}

async transferGold(to: string, amount: string) {
return sendTx(this.web3, this.goldToken.methods.transfer(to, amount), this.privateKey, {
to: this.goldTokenAddress,
Expand Down
55 changes: 42 additions & 13 deletions packages/faucet/src/database-helper.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* tslint:disable max-classes-per-file */
import { database } from 'firebase-admin'
import { DataSnapshot } from 'firebase-functions/lib/providers/database'
import Web3 from 'web3'
import { CeloAdapter } from './celo-adapter'
import { NetworkConfig } from './config'
import { ExecutionResult, logExecutionResult } from './metrics'
import { generateInviteCode, getPhoneHash, isE164Number, wait } from './utils'

export type Address = string
Expand Down Expand Up @@ -49,11 +51,26 @@ export async function processRequest(snap: DataSnapshot, pool: AccountPool, conf
} else if (request.type === RequestType.Invite) {
requestHandler = buildHandleInvite(request, snap, config)
} else {
throw new Error(`Unkown request type: ${request.type}`)
logExecutionResult(snap.key, ExecutionResult.InvalidRequestErr)
return ExecutionResult.InvalidRequestErr
}

const actionResult = await pool.doWithAccount(requestHandler)
if (actionResult === ActionResult.Ok) {
await snap.ref.update({ status: RequestStatus.Done })
logExecutionResult(snap.key, ExecutionResult.Ok)
return ExecutionResult.Ok
} else {
await snap.ref.update({ status: RequestStatus.Failed })
const result =
actionResult === ActionResult.NoFreeAccount
? ExecutionResult.NoFreeAccountErr
: ExecutionResult.ActionTimedOutErr
logExecutionResult(snap.key, result)
return result
}
const success = await pool.doWithAccount(requestHandler)
await snap.ref.update({ status: success ? RequestStatus.Done : RequestStatus.Failed })
} catch (err) {
logExecutionResult(snap.key, ExecutionResult.OtherErr)
console.error(`req(${snap.key}): ERROR proccessRequest`, err)
await snap.ref.update({ status: RequestStatus.Failed })
throw err
Expand Down Expand Up @@ -174,6 +191,12 @@ export interface PoolOptions {
}

const SECOND = 1000

enum ActionResult {
Ok,
NoFreeAccount,
ActionTimeout,
}
export class AccountPool {
constructor(
private db: database.Database,
Expand Down Expand Up @@ -201,17 +224,23 @@ export class AccountPool {
return this.accountsRef.once('value').then((snap) => snap.val())
}

async doWithAccount(action: (account: AccountRecord) => Promise<any>) {
async doWithAccount(action: (account: AccountRecord) => Promise<any>): Promise<ActionResult> {
const accountSnap = await this.tryLockAccountWithRetries()
if (accountSnap) {
try {
await withTimeout(this.options.actionTimeoutMS, () => action(accountSnap.val()))
} finally {
await accountSnap.child('locked').ref.set(false)
}
return true
} else {
return false
if (!accountSnap) {
return ActionResult.NoFreeAccount
}

try {
return withTimeout(
this.options.actionTimeoutMS,
async () => {
await action(accountSnap.val())
return ActionResult.Ok
},
() => ActionResult.ActionTimeout
)
} finally {
await accountSnap.child('locked').ref.set(false)
}
}

Expand Down
52 changes: 52 additions & 0 deletions packages/faucet/src/metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Logging } from '@google-cloud/logging'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So of course we do not have any standardization so to speak, but I generally would prefer for us not to directly log to GCP. Instead, GCP already aggregates the logs for us from STDOUT/STDERR and deals with doing so in a performant manner, batch, etc.

It also avoids us having to configure the client below, give the appropriate labels like project/region, and many more useful information such as execution id, severity etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I agree. But this is different.
The reason I'm using this library is to create my own LogEntry for stackdriver.
See that I'm using:

  • A different resource (fantasy function name on the metadata)
  • structured log entry, not a string

See the screenshot of an actual log:
image

This allow to easily create new metric with metric labels using structured fields.


// See https://firebase.google.com/docs/functions/config-env
const ProjectID = process.env.GCLOUD_PROJECT || 'celo-faucet'

const logging = new Logging({
projectId: ProjectID,
})
const log = logging.log('faucetMetrics')

const METADATA = {
resource: {
labels: {
function_name: 'faucetMetrics',
project_id: ProjectID,
region: 'us-central1',
},
type: 'cloud_function',
},
}

export enum ExecutionResult {
Ok = 'Ok',
/** Enqued Faucet Request has invalid type */
InvalidRequestErr = 'InvalidRequestErr',
/** Failed to obtain a free acount to faucet from */
NoFreeAccountErr = 'NoFreeAccountErr',
/** Faucet Action timed out */
ActionTimedOutErr = 'ActionTimedOutErr',
OtherErr = 'OtherErr',
}

/**
* Sends an entry but doesn't block
* (we don't want to block waiting for a metric to be sent)
*/
function noBlockingSendEntry(entryData: Record<string, any>) {
const entry = log.entry(METADATA, entryData)
log.write(entry).catch((err: any) => {
console.error('EventLogger: error sending entry', err)
})
}

export function logExecutionResult(snapKey: string, result: ExecutionResult) {
noBlockingSendEntry({
event: 'celo/faucet/result',
executionResult: result,
failed: result !== ExecutionResult.Ok,
snapKey,
message: `${snapKey}: Faucet result was ${result}`,
})
}
Loading