puppeteer: Is there a way to measure page cpu usage - puppeteer

I am trying to monitor CPU utilization with puppeteer. I can find handful relate solutions for page loading measurement and heap measuring but nothing to see the CPU utilization during the life of a page not only when the page is loading.
The same information is available in chrome under dev-tools/Performance monitor as
"CPU usage"

Dug through the code and figured I would share since it doesn't seem like this explanation has surfaced anywhere.
It looks like the relevant code in devtools is here:
https://source.chromium.org/chromium/chromium/src/+/main:out/Debug/gen/third_party/devtools-frontend/src/front_end/core/sdk/PerformanceMetricsModel.js;l=43?q=requestMetrics&ss=chromium%2Fchromium%2Fsrc
case "CumulativeTime" /* CumulativeTime */:
value = (data.lastTimestamp && data.lastValue) ?
Platform.NumberUtilities.clamp((metric.value - data.lastValue) * 1000 / (timestamp - data.lastTimestamp), 0, 1) :
0;
data.lastValue = metric.value;
data.lastTimestamp = timestamp;
break;
Devtools is polling for metrics, which appear to contain the cumulative time spent on a task since the metrics were enabled. CPU usage is the percent of time taken by all tasks since the last metrics request.
Sample code to log CPU usage over time:
interface CPUUsageSnapshot {
timestamp: number,
usage: number,
}
export interface CPUStats {
average: number,
snapshots: CPUUsageSnapshot[]
}
function processMetrics(metrics: GetMetricsResponse): {
timestamp: number,
activeTime: number
} {
const activeTime = metrics.metrics.filter(m => m.name.includes("Duration")).map(m => m.value).reduce((a, b) => a + b)
return {
timestamp: metrics.metrics.find(m => m.name === "Timestamp")?.value || 0,
activeTime
}
}
async function logMetrics(cdp: CDPSession, interval: number): Promise<() => Promise<CPUStats>> {
await cdp.send("Performance.enable", {
timeDomain: "timeTicks"
})
const { timestamp: startTime, activeTime: initialActiveTime } = processMetrics(await cdp.send("Performance.getMetrics"))
const snapshots: CPUUsageSnapshot[] = []
let cumulativeActiveTime = initialActiveTime
let lastTimestamp = startTime
const timer = setInterval(async () => {
const {timestamp, activeTime} = processMetrics(await cdp.send("Performance.getMetrics"))
const frameDuration = timestamp - lastTimestamp
let usage = (activeTime - cumulativeActiveTime) / frameDuration
cumulativeActiveTime = activeTime
if (usage > 1) usage = 1
snapshots.push({
timestamp,
usage
})
lastTimestamp = timestamp
}, interval)
return async () => {
clearInterval(timer)
await cdp.send("Performance.disable")
return {
average: cumulativeActiveTime / (lastTimestamp - startTime),
snapshots
}
}
}

Related

Foundry-function - compute a remain-to-do job count over month

I would like to compute a remain-to-do count per month, in foundry-function, from an objects-set having topicOpenTs and topicCloseTs properties (Both Timestamp type). Remain-to-do is a simple difference inflow value - outflow value for each month cumulated month after month (thank to stackoverflow topic 72883635).
I'm blocked because both aggregations don't have the same buckets : Inflow has 126 buckets (aka months) and outflow has 123 buckets (aka months).
A JS wait to proceed could be to create an object where Key is the key object of inflow and outflow aggregations and Value is the difference of inflow/outflow values for this month or 0 if it doesn't exist.
But TypeSCript complexify my way because I not used to manipulate strict Types and I get errors again and again. I tried during 5 hours and my code below is just too poor... I'm humbly asking for help.
My try lead me to this code but it doesn't work:
export class MyFunctions {
#Function()
public async cumulativeTopicsByMonth(topics: ObjectSet<MeFalWbTcardsTopics>): Promise<TwoDimensionalAggregation<IRange<Timestamp>, Double>> {
const bucketedInflow = await topics
.groupBy(topic => topic.topicOpenTs.byMonth())
.count();
//outflow
const bucketedOutflow = await topics
.groupBy(topic => topic.topicCloseTs.byMonth())
.count();
//************and what I would like to do but that doesn't work:
const allKeys = Array.from(new Set([...bucketedInflow.buckets.map(b => b.key), ...bucketedOutflow.buckets.map(b => b.key)]))
const allBuckets = allKeys.map(key => {
let inflow = bucketedInflow.buckets.find(inBucket => inBucket.key == key)
let outflow = bucketedOutflow.buckets.find(outBucket => outBucket.key == key)
return {key, value: inflow-outflow}
}
const remainToDo = {buckets: allBuckets}
********//end of the durty part with lot of Type Errors
//final steps similaire to [stackoverflow topic 72883635]
const sortedBucketedRtd = sortBuckets(remainToDo );
const cumulativeSortedBucketedRtd = cumulativeSum2D(sortedBucketedRtd );
return cumulativeSortedBucketedRtd
return {buckets: allBuckets}
}

Different volume levels do not work while using Web Audio API

public useAudio(base64EncodedAudio: any, loop: boolean, volume: number) {
let _this = this;
let audioFromString = this.base64ToBuffer(base64EncodedAudio);
this._context.decodeAudioData(audioFromString, function (buffer) {
_this.audioBuffer = buffer;
_this.PlaySound(loop, volume);
}, function (error) {
TelemetryClient.error(EventType.BASE64_DECODE_ERROR, "Error decoding sound string");
});
}
private PlaySound(loop: boolean, volume: number) {
this._source = this._context.createBufferSource();
let gainNode = this._context.createGain();
gainNode.gain.value = +(volume / 100).toFixed(2);
gainNode.connect(this._context.destination);
this._source.buffer = this._audioBuffer;
this._source.loop = loop;
this._source.connect(gainNode);
this._source.start(0);
}
gainNode.gain.value doesn't seem to work properly. Value 1 and 0.15 plays the sound at the full volume.
Am I missing anything here?
The gain AudioParam can handle very large values but if you want to lower the volume its value has to be somewhere in between 0 and 1. When the value is 0 no sound will be audible and a value of 1 will actually bypass the GainNode. Any larger value will amplify the sound. But depending on the browser and operating system you may not hear that since there might be a limiter in place before the sound hits your speakers.

No response when transaction is submitted to sawtooth intkey TP

I am trying to set up a transaction processor with hyperledger sawtooth. I tested my TP with sawtooth 1.0 and it worked fine. But when I used sawtooth 1.1 network, my transactions are not processed. It seems like the request does not reach the TP. I then tried the intkey TP from the sdk and that also has the same problem. I matched the transaction submit process from the documentation but for no good.
Sawtooth network docker file
version: "2.1"
services:
settings-tp:
image: hyperledger/sawtooth-settings-tp:1.1
container_name: sawtooth-settings-tp-default
depends_on:
- validator
entrypoint: settings-tp -vv -C tcp://validator:4004
validator:
image: hyperledger/sawtooth-validator:1.1
container_name: sawtooth-validator-default
expose:
- 4004
ports:
- "4004:4004"
# start the validator with an empty genesis batch
entrypoint: "bash -c \"\
sawadm keygen && \
sawtooth keygen my_key && \
sawset genesis -k /root/.sawtooth/keys/my_key.priv && \
sawadm genesis config-genesis.batch && \
sawtooth-validator -vv \
--endpoint tcp://validator:8800 \
--bind component:tcp://eth0:4004 \
--bind network:tcp://eth0:8800 \
\""
rest-api:
image: hyperledger/sawtooth-rest-api:1.1
container_name: sawtooth-rest-api-default
ports:
- "8008:8008"
depends_on:
- validator
entrypoint: sawtooth-rest-api -C tcp://validator:4004 --bind rest-api:8008
Transaction processor
/**
* Copyright 2016 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ------------------------------------------------------------------------------
*/
'use strict'
const { TransactionHandler } = require('sawtooth-sdk/processor/handler')
const {
InvalidTransaction,
InternalError
} = require('sawtooth-sdk/processor/exceptions')
const crypto = require('crypto')
const cbor = require('cbor')
// Constants defined in intkey specification
const MIN_VALUE = 0
const MAX_VALUE = 4294967295
const MAX_NAME_LENGTH = 20
const _hash = (x) =>
crypto.createHash('sha512').update(x).digest('hex').toLowerCase()
const INT_KEY_FAMILY = 'intkey'
const INT_KEY_NAMESPACE = _hash(INT_KEY_FAMILY).substring(0, 6)
const _decodeCbor = (buffer) =>
new Promise((resolve, reject) =>
cbor.decodeFirst(buffer, (err, obj) => (err ? reject(err) : resolve(obj)))
)
const _toInternalError = (err) => {
let message = (err.message) ? err.message : err
throw new InternalError(message)
}
const _setEntry = (context, address, stateValue) => {
let entries = {
[address]: cbor.encode(stateValue)
}
return context.setState(entries)
}
const _applySet = (context, address, name, value) => (possibleAddressValues) => {
let stateValueRep = possibleAddressValues[address]
let stateValue
if (stateValueRep && stateValueRep.length > 0) {
stateValue = cbor.decodeFirstSync(stateValueRep)
let stateName = stateValue[name]
if (stateName) {
throw new InvalidTransaction(
`Verb is "set" but Name already in state, Name: ${name} Value: ${stateName}`
)
}
}
// 'set' passes checks so store it in the state
if (!stateValue) {
stateValue = {}
}
stateValue[name] = value
return _setEntry(context, address, stateValue)
}
const _applyOperator = (verb, op) => (context, address, name, value) => (possibleAddressValues) => {
let stateValueRep = possibleAddressValues[address]
if (!stateValueRep || stateValueRep.length === 0) {
throw new InvalidTransaction(`Verb is ${verb} but Name is not in state`)
}
let stateValue = cbor.decodeFirstSync(stateValueRep)
if (stateValue[name] === null || stateValue[name] === undefined) {
throw new InvalidTransaction(`Verb is ${verb} but Name is not in state`)
}
const result = op(stateValue[name], value)
if (result < MIN_VALUE) {
throw new InvalidTransaction(
`Verb is ${verb}, but result would be less than ${MIN_VALUE}`
)
}
if (result > MAX_VALUE) {
throw new InvalidTransaction(
`Verb is ${verb}, but result would be greater than ${MAX_VALUE}`
)
}
// Increment the value in state by value
// stateValue[name] = op(stateValue[name], value)
stateValue[name] = result
return _setEntry(context, address, stateValue)
}
const _applyInc = _applyOperator('inc', (x, y) => x + y)
const _applyDec = _applyOperator('dec', (x, y) => x - y)
class IntegerKeyHandler extends TransactionHandler {
constructor () {
super(INT_KEY_FAMILY, ['1.0'], [INT_KEY_NAMESPACE])
}
apply (transactionProcessRequest, context) {
return _decodeCbor(transactionProcessRequest.payload)
.catch(_toInternalError)
.then((update) => {
//
// Validate the update
let name = update.Name
if (!name) {
throw new InvalidTransaction('Name is required')
}
if (name.length > MAX_NAME_LENGTH) {
throw new InvalidTransaction(
`Name must be a string of no more than ${MAX_NAME_LENGTH} characters`
)
}
let verb = update.Verb
if (!verb) {
throw new InvalidTransaction('Verb is required')
}
let value = update.Value
if (value === null || value === undefined) {
throw new InvalidTransaction('Value is required')
}
let parsed = parseInt(value)
if (parsed !== value || parsed < MIN_VALUE || parsed > MAX_VALUE) {
throw new InvalidTransaction(
`Value must be an integer ` +
`no less than ${MIN_VALUE} and ` +
`no greater than ${MAX_VALUE}`)
}
value = parsed
// Determine the action to apply based on the verb
let actionFn
if (verb === 'set') {
actionFn = _applySet
} else if (verb === 'dec') {
actionFn = _applyDec
} else if (verb === 'inc') {
actionFn = _applyInc
} else {
throw new InvalidTransaction(`Verb must be set, inc, dec not ${verb}`)
}
let address = INT_KEY_NAMESPACE + _hash(name).slice(-64)
// Get the current state, for the key's address:
let getPromise = context.getState([address])
// Apply the action to the promise's result:
let actionPromise = getPromise.then(
actionFn(context, address, name, value)
)
// Validate that the action promise results in the correctly set address:
return actionPromise.then(addresses => {
if (addresses.length === 0) {
throw new InternalError('State Error!')
}
console.log(`Verb: ${verb} Name: ${name} Value: ${value}`)
})
})
}
}
module.exports = IntegerKeyHandler
SendTransaction
const {createContext, CryptoFactory} = require('sawtooth-sdk/signing')
const cbor = require('cbor')
const {createHash} = require('crypto')
const {protobuf} = require('sawtooth-sdk')
const crypto = require('crypto')
// Creating a Private Key and Signer
const context = createContext('secp256k1')
const privateKey = context.newRandomPrivateKey()
const signer = new CryptoFactory(context).newSigner(privateKey)
const _hash = (x) => crypto.createHash('sha512').update(x).digest('hex').toLowerCase()
// Encoding Your Payload
const payload = {
Verb: 'get',
Name: 'foo',
Value: null
}
const payloadBytes = cbor.encode(payload)
let familyAddr = _hash('intkey').substring(0, 6);
let nameAddr = _hash(payload.Name).slice(-64);
let addr = familyAddr + nameAddr;
console.log(addr);
// Create the Transaction Header
const transactionHeaderBytes = protobuf.TransactionHeader.encode({
familyName: 'intkey',
familyVersion: '1.0',
inputs: [addr],
outputs: [addr],
signerPublicKey: signer.getPublicKey().asHex(),
batcherPublicKey: signer.getPublicKey().asHex(),
dependencies: [],
payloadSha512: createHash('sha512').update(payloadBytes).digest('hex')
}).finish()
// Create the Transaction
const signature = signer.sign(transactionHeaderBytes)
const transaction = protobuf.Transaction.create({
header: transactionHeaderBytes,
headerSignature: signature,
payload: payloadBytes
})
// Create the BatchHeader
const transactions = [transaction]
const batchHeaderBytes = protobuf.BatchHeader.encode({
signerPublicKey: signer.getPublicKey().asHex(),
transactionIds: transactions.map((txn) => txn.headerSignature),
}).finish()
// Create the Batch
const headerSignature = signer.sign(batchHeaderBytes)
const batch = protobuf.Batch.create({
header: batchHeaderBytes,
headerSignature: headerSignature,
transactions: transactions
})
// Encode the Batch(es) in a BatchList
const batchListBytes = protobuf.BatchList.encode({
batches: [batch]
}).finish()
// Submitting Batches to the Validator
const request = require('request')
request.post({
url: 'http://localhost:8008/batches',
body: batchListBytes,
headers: {'Content-Type': 'application/octet-stream'}
}, (err, response) => {
if (err) return console.log(err)
console.log(response.body)
})
There are architectural differences in the Hyperledger Sawtooth when moving from 1.0 to 1.1. One major difference is that the consensus engine is moved outside the validator service. In your docker-compose file, there is no consensus engine component also the validator service is not listening on a port for the consensus engine.
The consensus engine drives the block creation. For example, a timer expiry event in the PoET will cause the validator to create a block, validate, broadcast to other members in the network. Also, a confirmation from the consensus engine will make the validator service to commit the block to the blockchain.
Please find an example docker-compose file with the PoET consensus engine here https://github.com/hyperledger/sawtooth-core/blob/1-1/docker/compose/sawtooth-default-poet.yaml . Additionally you may try out https://github.com/hyperledger/sawtooth-core/blob/1-1/docker/compose/sawtooth-default.yaml for local dev test.
There is an explanation in the Hyperledger Sawtooth FAQ for upgrading from 1.0 version to the version 1.1 https://sawtooth.hyperledger.org/faq/upgrade/#id1 . Please feel free to ask more questions or suggestions to update these documentation.
You can also refer to the official documentation to learn in detail for different versions here https://sawtooth.hyperledger.org/docs/.

Too tidious hooks when querying in REST. Any ideas?

I've just started using feathers to build REST server. I need your help for querying tips. Document says
When used via REST URLs all query values are strings. Depending on the service the values in params.query might have to be converted to the right type in a before hook. (https://docs.feathersjs.com/api/databases/querying.html)
, which puzzles me. find({query: {value: 1} }) does mean value === "1" not value === 1 ? Here is example client side code which puzzles me:
const feathers = require('#feathersjs/feathers')
const fetch = require('node-fetch')
const restCli = require('#feathersjs/rest-client')
const rest = restCli('http://localhost:8888')
const app = feathers().configure(rest.fetch(fetch))
async function main () {
const Items = app.service('myitems')
await Items.create( {name:'one', value:1} )
//works fine. returns [ { name: 'one', value: 1, id: 0 } ]
console.log(await Items.find({query:{ name:"one" }}))
//wow! no data returned. []
console.log(await Items.find({query:{ value:1 }})) // []
}
main()
Server side code is here:
const express = require('#feathersjs/express')
const feathers = require('#feathersjs/feathers')
const memory = require('feathers-memory')
const app = express(feathers())
.configure(express.rest())
.use(express.json())
.use(express.errorHandler())
.use('myitems', memory())
app.listen(8888)
.on('listening',()=>console.log('listen on 8888'))
I've made hooks, which works all fine but it is too tidious and I think I missed something. Any ideas?
Hook code:
app.service('myitems').hooks({
before: { find: async (context) => {
const value = context.params.query.value
if (value) context.params.query.value = parseInt(value)
return context
}
}
})
This behaviour depends on the database and ORM you are using. Some that have a schema (like feathers-mongoose, feathers-sequelize and feathers-knex), will convert values like that automatically.
Feathers itself does not know about your data format and most adapters (like the feathers-memory you are using here) do a strict comparison so they will have to be converted. The usual way to deal with this is to create some reusable hooks (instead of one for each field) like this:
const queryToNumber = (...fields) => {
return context => {
const { params: { query = {} } } = context;
fields.forEach(field => {
const value = query[field];
if(value) {
query[field] = parseInt(value, 10)
}
});
}
}
app.service('myitems').hooks({
before: {
find: [
queryToNumber('age', 'value')
]
}
});
Or using something like JSON schema e.g. through the validateSchema common hook.

would mysql js pool cause transaction deadlock

I am designing an ticket system using NodeJS and Mysql-Promise.
For safety and consistency, I want to use Transaction but I worry about the deadlock.
The scenario is
People can buy multi tickets in one order
I would check every tickets whether the rest amount of tickets is enough or not.
I update the amount of tickets
I create the order
The code is
await ctx.conn.beginTransaction()
// check amount of tickets
let ticketIdList = ctx.body.tickets.map(t => t._id)
let ticketList = await models.ticket.findTicketList(ctx.conn, ticketIdList)
for (let t in ticketList) {
let ticketBook = ctx.body.tickets.filter(tB => tB._id === t._id)
if (t.perMinSaleCount > ticketBook.count || t.perMaxSaleCount < ticketBook.count) {
ctx.stats = 400
return ctx.body = {
error: "FailTickets"
}
}
if (t.sold + ticketBook.count > t.total) {
ctx.stats = 400
return ctx.body = {
error: "SoldOut"
}
}
}
// update the tickets
await Promise.all(
ctx.body.tickets.map(t => models.ticket.reserveTicket(ctx.conn, t._id, t.count))
)
// create Order
await model.order.createOrder(...)
await ctx.conn.commit()
My big concern is the ctx.conn is create by pool
mysql.createPool({
......
})
Would // await Promise.all(ctx.body.tickets.map(t => models.ticket.reserveTicket(ctx.conn, t._id, t.count))) this lines make sql in multi connections and cause deadlock ?
How to properly use conn.beginTransaction()?
Thanks