How to enable and disable perf_hook based on Flag - node.js

We have large code with lot of function called in series . For performance improvement we want to measure the time between to specific point.
Step by step we want to make improvement. I was thinking of making user of perf_hook
Our best use case will be using as below , as per the lib
const { PerformanceObserver, performance } = require('perf_hooks');
const obs = new PerformanceObserver((items) => {
console.log(items.getEntries()[0].duration);
performance.clearMarks();
});
obs.observe({ type: 'measure' });
performance.measure('Start to Now');
performance.mark('A');
doSomeLongRunningProcess(() => {
performance.measure('A to Now', 'A');
performance.mark('B');
performance.measure('A to B', 'A', 'B');
});
I looked and searched in lib as well as web , did not find any env variable to enable or disable the perf_hook, so enable and disable the code in PROD stage.

playing around the code .
Just putting the declaration based on condition
let obs = null;
//ACTIVATE or DEACTIVATE
if( true || false) {
obs = new PerformanceObserver((items) => {
console.log(items.getEntries()[0].duration);
performance.clearMarks();
});
obs.observe({ type: 'measure' });
}

Related

Best way to organize firebase writes on update trigger

There may be more than one correct answer to this question, but here's my issue: I have a user document in firebase with many fields that can be updated and which interact in different ways. If one field is updated, it may require a change to another field on the backend. Is it better to have a whole bunch of if statements each with their own write action if the condition is met or, or do single write at the end of the function for all the fields that might change. If a field does not change, I would have to write its original value back to itself. That seems clunky, but so does the other option. Am I missing a third option? What is the best practice here?
Here's an example of what I'm talking about. Updating fields one at a time is what I have now, which looks like this:
export const userUpdate = functions.firestore
.document("users/{userID}")
.onUpdate(async (change) => {
const beforeData = change.before.data();
const afterData = change.after.data();
// user levels up and gets more HP
if(beforeData.userLevel != afterData.userLevel){
const newMaxHP = 15 + 5 * afterData.userLevel;
change.after.ref.update({
maxHp: newMaxHP
})
}
//update user rating
if (beforeData.numberOfRatings != afterData.numberOfRatings) {
const newRating = placerRating(beforeData.userRating, beforeData.numberOfRatings, afterData.latestRating);
change.after.ref.update({
userRating: newRating
})
}
//replenish user funds from zero
if (afterData.money == 0){
change.after.ref.update({
money: 20
})
}
If I did it all in a single write, the if statements would assign a value to a variable, but not update the firestore document. Each if statement would include an else statement assigning the variable to the field's original value. There would be a single write at the end like this:
change.after.ref.update({
maxHp: newMaxHP,
userRating: newRating,
money: 20
})
I hope that helps.
[edit to add follow-up question about updating a map value]
#Dharmaraj's answer works great, but I'm struggling to apply it when updating a map value. BTW - I'm using Typescript.
Before using #Dharmaraj's solution, I was doing this:
admin.firestore().collection("users").doc(lastPlayerAttacker).update({
"equipped.weapon.usesLeft": admin.firestore.FieldValue.increment(-1)
});
Using the update object, I'm trying it like this, but I get the error "Object is of type 'unknown'"
const lastPlayerUpdates:{[key:string]:unknown} = {};
lastPlayerUpdates.equipped.weapon.usesLeft = admin.firestore.FieldValue.increment(-1);
admin.firestore().collection("users").doc(lastPlayerAttacker).update(lastPlayerUpdates);
Any advice on how to fix it?
Every time you call update(), you are being charged for 1 write operation. It'll be best to accumulate all updated fields in an object and then update the document only once as it'll be more efficient too. Try refactoring the code as shown below:
export const userUpdate = functions.firestore
.document("users/{userID}")
.onUpdate(async (change) => {
const beforeData = change.before.data();
const afterData = change.after.data();
const updatedData = {};
// user levels up and gets more HP
if (beforeData.userLevel != afterData.userLevel) {
const newMaxHP = 15 + 5 * afterData.userLevel;
updatedData.maxHp = newMaxHP;
}
//update user rating
if (beforeData.numberOfRatings != afterData.numberOfRatings) {
const newRating = placerRating(beforeData.userRating, beforeData.numberOfRatings, afterData.latestRating);
updatedData.userRating = newRating;
}
//replenish user funds from zero
if (afterData.money == 0) {
updatedData.money = 20;
}
await change.after.ref.update(updatedData);
console.log("Data updated");
return null;
})

Tiptap how to create a paragraph (p) on Shift-Enter, instead of a br?

Using TipTap, I'm trying to avoid adding a <br />, but create a <p></p> instead, with the focus inside that <p>|</p> when the user hit shift-Enter but I can't make it work.
Here's what I did so far:
new (class extends Extension {
keys () {
return {
'Shift-Enter' (state, dispatch, view) {
const { schema, tr } = view.state
const paragraph = schema.nodes.paragraph
console.log(tr.storedMarks)
const transaction = tr.deleteSelection().replaceSelectionWith(paragraph.create(), true).scrollIntoView()
view.dispatch(transaction)
return true
}
}
}
})()
How can I do this?
I don't know if this is still relevant but as I was looking for the same thing, I found two ways to make this work.
NOTE:
I'm using tiptap v2, if that's not a problem, then:
I overrode the HardBreak extension, since it's the one that use the Shift-Enter keybinding. It looks something like;
const CustomHardBreak = HardBreak.extend({
addKeyboardShortcuts() {
return {
"Mod-Enter": () => this.editor.commands.setHardBreak(),
"Shift-Enter": () => this.editor.commands.addNewline(),
};
},
});
And used it like so;
editor = new Editor({
extensions: [
customNewline,
CustomHardBreak,
]
});
Use the default editor command createParagraphNear. E.g this.editor.commands.createParagraphNear()
I tried creating a custom extension from your code and ended up with something similar to the command above, i.e;
export const customNewline = Extension.create({
name: "newline",
priority: 1000, // Optional
addCommands() {
return {
addNewline:
() =>
({ state, dispatch }) => {
const { schema, tr } = state;
const paragraph = schema.nodes.paragraph;
const transaction = tr
.deleteSelection()
.replaceSelectionWith(paragraph.create(), true)
.scrollIntoView();
if (dispatch) dispatch(transaction);
return true;
},
};
},
addKeyboardShortcuts() {
return {
"Shift-Enter": () => this.editor.commands.addNewline(),
};
},
});
And added this as an extension in my editor instance.
PS:
They both work, almost exactly the same, I haven't found a difference yet. But there's somewhat of a 'catch' if you would call it that; Both these methods don't work on empty lines/nodes, a character has to be added before the cursor for it to work, any character, even a space.
In TipTap 2.0 I am able to use this custom extension:
const ShiftEnterCreateExtension = Extension.create({
addKeyboardShortcuts() {
return {
"Shift-Enter": ({ editor }) => {
editor.commands.enter();
return true;
},
};
},
});
To make shift + enter behave like enter.
In my case I actually wanted enter to do something different. So I use prosemirror events to set a ref flag on whether shift was pressed. Than I check that flag under the "Enter" keyboard event -- which could be triggered normally or through the shift + enter extension.

How to provide custom names for page view events in Azure App Insights?

By default App Insights use page title as event name. Having dynamic page names, like "Order 32424", creates insane amount of event types.
Documentation on the matter says to use trackEvent method, but there are no examples.
appInsights.trackEvent("Edit button clicked", { "Source URL": "http://www.contoso.com/index" })
What is the best approach? It would be perfect to have some sort of map/filter which would allow to modify event name for some pages to the shared name, like "Order 23424" => "Order", at the same time to leave most pages as they are.
You should be able to leverage telemetry initializer approach to replace certain pattern in the event name with the more "common" version of that name.
Here is the example from Application Insights JS SDK GitHub on how to modify pageView's data before it's sent out. With the slight modification you may use it to change event names based on their appearance:
window.appInsights = appInsights;
...
// Add telemetry initializer
appInsights.queue.push(function () {
appInsights.context.addTelemetryInitializer(function (envelope) {
var telemetryItem = envelope.data.baseData;
// To check the telemetry item’s type:
if (envelope.name === Microsoft.ApplicationInsights.Telemetry.PageView.envelopeType) {
// this statement removes url from all page view documents
telemetryItem.url = "URL CENSORED";
}
// To set custom properties:
telemetryItem.properties = telemetryItem.properties || {};
telemetryItem.properties["globalProperty"] = "boo";
// To set custom metrics:
telemetryItem.measurements = telemetryItem.measurements || {};
telemetryItem.measurements["globalMetric"] = 100;
});
});
// end
...
appInsights.trackPageView();
appInsights.trackEvent(...);
With help of Dmitry Matveev I've came with the following final code:
var appInsights = window.appInsights;
if (appInsights && appInsights.queue) {
function adjustPageName(item) {
var name = item.name.replace("AppName", "");
if (name.indexOf("Order") !== -1)
return "Order";
if (name.indexOf("Product") !== -1)
return "Shop";
// And so on...
return name;
}
// Add telemetry initializer
appInsights.queue.push(function () {
appInsights.context.addTelemetryInitializer(function (envelope) {
var telemetryItem = envelope.data.baseData;
// To check the telemetry item’s type:
if (envelope.name === Microsoft.ApplicationInsights.Telemetry.PageView.envelopeType || envelope.name === Microsoft.ApplicationInsights.Telemetry.PageViewPerformance.envelopeType) {
// Do not track admin pages
if (telemetryItem.name.indexOf("Admin") !== -1)
return false;
telemetryItem.name = adjustPageName(telemetryItem);
}
});
});
}
Why this code is important? Because App Insights use page titles by default as Name for PageView, so you would have hundreds and thousands of different events, like "Order 123132" which would make further analysis (funnel, flows, events) meaningless.
Key highlights:
var name = item.name.replace("AppName", ""); If you put your App/Product name in title, you probably want to remove it from you event name, because it would just repeat itself everywhere.
appInsights && appInsights.queue you should check for appInsights.queue because for some reason it can be not defined and it would cause an error.
if (telemetryItem.name.indexOf("Admin") !== -1) return false; returning false will cause event to be not recorded at all. There certain events/pages you most likely do not want to track, like admin part of website.
There are two types of events which use page title as event name: PageView
and PageViewPerformance. It makes sense to modify both of them.
Here's one work-around, if you're using templates to render your /orders/12345 pages:
appInsights.trackPageView({name: TEMPLATE_NAME });
Another option, perhaps better suited for a SPA with react-router:
const Tracker = () => {
let {pathname} = useLocation();
pathname = pathname.replace(/([/]orders[/])([^/]+), "$1*"); // handle /orders/NN/whatever
pathname = pathname.replace(/([/]foo[/]bar[/])([^/]+)(.*)/, "$1*"); // handle /foo/bar/NN/whatever
useEffect(() => {
appInsights.trackPageView({uri: pathname});
}, [pathname]);
return null;
}

How to prevent malicious Meteor subscriptions using check

I found an important security fault in my meteor app regarding subscriptions (maybe methods are also affected by this).
Even though I use the check package and check() assuring that the correct parameters data types are received inside the publication, I have realised that if a user maliciously subscribes to that subscription with wrong parameter data types it is affecting all other users that are using the same subscription because the meteor server is not running the publication while the malicious user is using incorrect parameters.
How can I prevent this?
Packages used:
aldeed:collection2-core#2.0.1
audit-argument-checks#1.0.7
mdg:validated-method
and npm
import { check, Match } from 'meteor/check';
Server side:
Meteor.publish('postersPub', function postersPub(params) {
check(params, {
size: String,
section: String,
});
return Posters.find({
section: params.section,
size: params.size === 'large' ? 'large' : 'small',
}, {
// fields: { ... }
// sort: { ... }
});
});
Client side:
// in the template:
Meteor.subscribe('postersPub', { size: 'large', section: 'movies' });
// Malicious user in the browser console:
Meteor.subscribe('postersPub', { size: undefined, section: '' });
Problem: The malicious user subscription is preventing all other users of getting answer from their postersPub subscriptions.
Extra note: I've also tried wrapping the check block AND the whole publication with a try catch and it doesn't change the effect. The error disappears from the server console, but the other users keep being affected and not getting data from the subscription that the malicious user is affecting.
Check method and empty strings
There is one thing to know about check and strings which is, that it accepts empty strings like '' which you basically showed in your malicious example.
Without guarantee to solve your publication issue I can at least suggest you to modify your check code and include a check for non-empty Strings.
A possible approach could be:
import { check, Match } from 'meteor/check';
const nonEmptyString = Match.Where(str => typeof str === 'string' && str.length > 0);
which then can be used in check like so:
check(params, {
size: nonEmptyString,
section: nonEmptyString,
});
Even more strict checks
You may be even stricter with accepted parameters and reduce them to a subset of valid entries. For example:
const sizes = ['large', 'small'];
const nonEmptyString = str => typeof str === 'string' && str.length > 0;
const validSize = str => nonEmptyString(str) && sizes.indexOf( str) > -1;
check(params, {
size: Match.Where(validSize),
section: Match.Where(nonEmptyString),
});
Note, that this also helps you to avoid query logic based on the parameter. You can change the following code
const posters = Posters.find({
section: params.section,
size: params.size === 'large' ? 'large' : 'small',
}, {
// fields: { ... }
// sort: { ... }
});
to
const posters = Posters.find({
section: params.section,
size: params.size,
}, {
// fields: { ... }
// sort: { ... }
});
because the method does anyway accept only one of large or small as parameters.
Fallback on undefined cursors in publications
Another pattern that can support you preventing publication errors is to call this.ready() if the collection returned no cursor (for whatever reason, better is to write good tests to prevent you from these cases).
const posters = Posters.find({
section: params.section,
size: params.size === 'large' ? 'large' : 'small',
}, {
// fields: { ... }
// sort: { ... }
});
// if we have a cursor with count
if (posters && posters.count && posters.count() >= 0)
return posters;
// else signal the subscription
// that we are ready
this.ready();
Combined code example
Applying all of the above mentioned pattern would make your function look like this:
import { check, Match } from 'meteor/check';
const sizes = ['large', 'small'];
const nonEmptyString = str => typeof str === 'string' && str.length > 0;
const validSize = str => nonEmptyString(str) && sizes.indexOf( str) > -1;
Meteor.publish('postersPub', function postersPub(params) {
check(params, {
size: Match.Where(validSize),
section: Match.Where(nonEmptyString),
});
const posters = Posters.find({
section: params.section,
size: params.size,
}, {
// fields: { ... }
// sort: { ... }
});
// if we have a cursor with count
if (posters && posters.count && posters.count() >= 0)
return posters;
// else signal the subscription
// that we are ready
this.ready();
});
Summary
I for myself found that with good check matches and this.ready() the problems with publications have been reduced to a minimum in my applications.

How to keep previous data in Leaflet Realtime

I am trying to use Leaflet realtime plugin (https://github.com/perliedman/leaflet-realtime), they mentioned in the documentation that we can keep previous updates by adding start:false in the constructor.
var map = L.map('map'),
realtime = L.realtime({
url: 'https://wanderdrone.appspot.com/',
crossOrigin: true,
type: 'json'
}, {
interval: 3 * 1000, start:false
}).addTo(map);
Anyone have better idea on how to do that ?
plnkr has a good demo:
http://plnkr.co/edit/NmtcUa?p=preview
If you set start: false, automatic updates will be disabled. This means you will have to call the layer's update method yourself, providing any GeoJSON data you want to add or update; you can also remove added features with the remove method. These methods can be used if you want to use something other than server polling.
You can use the following code that I copied from plnkr while changed a little bit, because L.realtime inherit L.geoJson. And L.geoJson have 'layeradd'.
realtime.on('layeradd', function(e) {
var coordPart = function(v, dirs) {
return dirs.charAt(v >= 0 ? 0 : 1) +
(Math.round(Math.abs(v) * 100) / 100).toString();
},
popupContent = function(fId) {
var feature = e.features[fId],
c = feature.geometry.coordinates;
return 'Wander drone at ' +
coordPart(c[1], 'NS') + ', ' + coordPart(c[0], 'EW');
},
bindFeaturePopup = function(fId) {
realtime.getLayer(fId).bindPopup(popupContent(fId));
},
updateFeaturePopup = function(fId) {
realtime.getLayer(fId).getPopup().setContent(popupContent(fId));
};
map.fitBounds(realtime.getBounds(), {maxZoom: 3});
Object.keys(e.enter).forEach(bindFeaturePopup);
Object.keys(e.update).forEach(updateFeaturePopup);
});

Resources