191 строка
5.0 KiB
JavaScript
Executable File
191 строка
5.0 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
'use strict'
|
|
|
|
var fs = require('fs')
|
|
|
|
module.exports = {
|
|
run: run,
|
|
countAccounts: [
|
|
'SELECT COUNT(*) AS count',
|
|
'FROM accounts',
|
|
'WHERE createdAt < ?;'
|
|
].join('\n'),
|
|
countVerifiedAccounts: [
|
|
'SELECT COUNT(*) AS count',
|
|
'FROM accounts',
|
|
'WHERE createdAt < ?',
|
|
'AND emailVerified = true;'
|
|
].join('\n'),
|
|
countAccountsWithTwoOrMoreDevices: [
|
|
'SELECT COUNT(*) AS count',
|
|
'FROM (',
|
|
' SELECT a.uid',
|
|
' FROM accounts AS a',
|
|
' INNER JOIN sessionTokens AS s',
|
|
' ON a.uid = s.uid',
|
|
' WHERE a.createdAt < ?',
|
|
' GROUP BY (a.uid)',
|
|
' HAVING COUNT(s.tokenId) > 1',
|
|
') AS sub;'
|
|
].join('\n'),
|
|
countAccountsWithThreeOrMoreDevices: [
|
|
'SELECT COUNT(*) AS count',
|
|
'FROM (',
|
|
' SELECT a.uid',
|
|
' FROM accounts AS a',
|
|
' INNER JOIN sessionTokens AS s',
|
|
' ON a.uid = s.uid',
|
|
' WHERE a.createdAt < ?',
|
|
' GROUP BY (a.uid)',
|
|
' HAVING COUNT(s.tokenId) > 2',
|
|
') AS sub;'
|
|
].join('\n'),
|
|
countAccountsWithMobileDevice: [
|
|
'SELECT COUNT(DISTINCT a.uid) AS count',
|
|
'FROM accounts AS a',
|
|
'INNER JOIN sessionTokens AS s',
|
|
'ON a.uid = s.uid',
|
|
'WHERE a.createdAt < ?',
|
|
'AND s.uaDeviceType = \'mobile\';'
|
|
].join('\n')
|
|
}
|
|
|
|
if (require.main === module) {
|
|
module.exports.run(
|
|
parseConfigFile(process.argv[2] || '/etc/gather_basic_metrics.conf')
|
|
)
|
|
}
|
|
|
|
function run (config, now) {
|
|
now = now || new Date()
|
|
var lastMidnight = Date.UTC(
|
|
now.getUTCFullYear(),
|
|
now.getUTCMonth(),
|
|
now.getUTCDate(),
|
|
0, 0, 0, 0
|
|
)
|
|
var os = require('os')
|
|
var log = require('../lib/logging')('bin.metrics')
|
|
var mysql = require('../lib/db/mysql')(log, require('../db-server').errors)
|
|
var self = this
|
|
var db
|
|
|
|
return mysql.connect({
|
|
master: {
|
|
host: config.General.db_dnsname,
|
|
user: config.General.db_username,
|
|
password: config.General.db_password,
|
|
database: config.General.db_name
|
|
},
|
|
slave: {
|
|
host: config.General.db_dnsname,
|
|
user: config.General.db_username,
|
|
password: config.General.db_password,
|
|
database: config.General.db_name
|
|
},
|
|
patchKey: 'schema-patch-level'
|
|
})
|
|
.then(function (result) {
|
|
db = result
|
|
return db.readMultiple([
|
|
{ sql: 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED' },
|
|
{ sql: 'START TRANSACTION' },
|
|
metricsQuery('countAccounts'),
|
|
metricsQuery('countVerifiedAccounts'),
|
|
metricsQuery('countAccountsWithTwoOrMoreDevices'),
|
|
metricsQuery('countAccountsWithThreeOrMoreDevices'),
|
|
metricsQuery('countAccountsWithMobileDevice'),
|
|
], { sql: 'COMMIT' })
|
|
})
|
|
.then(function (results) {
|
|
assertResults(results, 2, 6)
|
|
fs.appendFileSync('/media/ephemeral0/fxa-admin/basic_metrics.log', JSON.stringify({
|
|
hostname: os.hostname(),
|
|
pid: process.pid,
|
|
op: 'account_totals',
|
|
total_accounts: results[2][0].count,
|
|
total_verified_accounts: results[3][0].count,
|
|
total_accounts_with_two_or_more_devices: results[4][0].count,
|
|
total_accounts_with_three_or_more_devices: results[5][0].count,
|
|
total_accounts_with_mobile_device: results[6][0].count,
|
|
time: (new Date(lastMidnight)).toISOString(),
|
|
v: 0
|
|
}) + '\n')
|
|
db.close()
|
|
})
|
|
.catch(function (error) {
|
|
log.error('metrics.run', error)
|
|
db.close()
|
|
})
|
|
|
|
function metricsQuery (queryName) {
|
|
return { sql: self[queryName], params: [ lastMidnight ] }
|
|
}
|
|
}
|
|
|
|
function assertResults (results, firstIndex, lastIndex) {
|
|
results.filter(function (result, index) {
|
|
return index >= firstIndex && index <= lastIndex
|
|
}).forEach(assertResult)
|
|
}
|
|
|
|
function assertResult (result) {
|
|
if (Array.isArray(result) && result.length === 1 && result[0].count >= 0) {
|
|
return
|
|
}
|
|
|
|
throw new Error('unexpected metrics query result format, should be [ { count: n } ]')
|
|
}
|
|
|
|
// Very rudimentary parser for the following config file format:
|
|
// [General]
|
|
// db_dnsname: foo
|
|
// db_username: bar
|
|
// db_password: baz
|
|
// db_name: qux
|
|
function parseConfigFile (path) {
|
|
var currentSection
|
|
|
|
return fs.readFileSync(path, { encoding: 'utf8' })
|
|
.split('\n')
|
|
.map(trim)
|
|
.filter(filterConfig)
|
|
.reduce(reduceConfig, {})
|
|
|
|
function reduceConfig (parsed, line) {
|
|
var isSectionName = line.match(/^\[(.+)\]$/)
|
|
|
|
if (isSectionName) {
|
|
currentSection = isSectionName[1]
|
|
|
|
if (! parsed[currentSection]) {
|
|
parsed[currentSection] = {}
|
|
}
|
|
} else {
|
|
var setting = line.split(':').map(trim)
|
|
|
|
if (! currentSection || setting.length === 0 || setting.length > 2) {
|
|
throw new Error('unexpected config file format')
|
|
}
|
|
|
|
parsed[currentSection][setting[0]] = setting[1]
|
|
}
|
|
|
|
return parsed
|
|
}
|
|
}
|
|
|
|
function trim (string) {
|
|
return string.trim()
|
|
}
|
|
|
|
function filterConfig (line) {
|
|
return line && line[0] !== '#' && line[0] !== ';'
|
|
}
|
|
|