Skip to content

Commit c6ca8f2

Browse files
committed
added logPerf() calls during /publicEventStats
1 parent 204dbcf commit c6ca8f2

File tree

6 files changed

+151
-121
lines changed

6 files changed

+151
-121
lines changed

cloud/functions/src/functions/firestore/firestore-utils.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {ISODatetime} from "../../../../../shared/type-utils";
88
import {sortBy} from "lodash";
99
import {firestore} from "firebase-admin";
1010
import DocumentReference = firestore.DocumentReference;
11+
import {logPerf} from "../http/utils";
1112

1213
export type EventFamilyToken = {
1314
families: string[],
@@ -65,8 +66,10 @@ export async function ensureTalkFeedbackViewerTokenIsValidThenGetFeedbacks(event
6566
}
6667

6768
export async function eventTalkStatsFor(eventId: string) {
68-
const eventTalkStatsPerTalkId = (await db.doc(`events/${eventId}/talksStats-allInOne/self`).get()).data() as Record<string, TalkStats>;
69-
return Object.values(eventTalkStatsPerTalkId);
69+
return logPerf(`eventTalkStatsFor(${eventId})`, async () => {
70+
const eventTalkStatsPerTalkId = (await db.doc(`events/${eventId}/talksStats-allInOne/self`).get()).data() as Record<string, TalkStats>;
71+
return Object.values(eventTalkStatsPerTalkId);
72+
})
7073
}
7174

7275
export async function eventLastUpdateRefreshed<T extends {[field in keyof T]: ISODatetime|null}>(
Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import {db} from "../../../firebase";
22
import {ConferenceDescriptor} from "../../../../../../shared/conference-descriptor.firestore";
3+
import {logPerf} from "../../http/utils";
34

45

56
export async function getEventDescriptor(eventId: string) {
6-
const eventDescriptorSnap = await db
7-
.collection("events").doc(eventId)
8-
.collection("event-descriptor").doc("self")
9-
.get();
10-
11-
const eventDescriptor = eventDescriptorSnap.data() as ConferenceDescriptor;
12-
return eventDescriptor;
7+
return logPerf(`getEventDescriptor(${eventId})`, async () => {
8+
const eventDescriptorSnap = await db.doc(`events/${eventId}/event-descriptor/self`).get();
9+
const eventDescriptor = eventDescriptorSnap.data() as ConferenceDescriptor;
10+
return eventDescriptor;
11+
})
1312
}

cloud/functions/src/functions/firestore/services/publicTokens-utils.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
PublicToken
66
} from "../../../../../../shared/public-tokens";
77
import {match, P} from "ts-pattern";
8+
import {logPerf} from "../../http/utils";
89

910

1011
export function isFamilyEventsStatsToken(publicToken: PublicToken): publicToken is FamilyEventsStatsAccessToken {
@@ -30,13 +31,17 @@ async function getPublicTokenBySecret<T>(secretToken: string, transformer: (publ
3031
}
3132

3233
export async function getFamilyEventsStatsToken(secretToken: string) {
33-
return getPublicTokenBySecret(secretToken,
34+
return logPerf("getFamilyEventsStatsToken()", async () => {
35+
return getPublicTokenBySecret(secretToken,
3436
publicToken => isFamilyEventsStatsToken(publicToken) || isFamilyOrganizerToken(publicToken)?publicToken:undefined,
35-
"family events stats token")
37+
"family events stats token")
38+
})
3639
}
3740

3841
export async function getFamilyOrganizerToken(secretToken: string) {
39-
return getPublicTokenBySecret(secretToken,
40-
publicToken => isFamilyOrganizerToken(publicToken)?publicToken:undefined,
41-
"family organizer token")
42+
return logPerf("getFamilyOrganizerToken()", async () => {
43+
return getPublicTokenBySecret(secretToken,
44+
publicToken => isFamilyOrganizerToken(publicToken) ? publicToken : undefined,
45+
"family organizer token")
46+
})
4247
}

cloud/functions/src/functions/firestore/services/talk-utils.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
PerPublicUserIdFeedbackRatings
77
} from "../../../../../../shared/conference-organizer-space.firestore";
88
import {ISOLocalDate} from "../../../../../../shared/type-utils";
9+
import {logPerf} from "../../http/utils";
910

1011

1112
export async function getTalkDetails(eventId: string) {
@@ -65,15 +66,17 @@ export async function getEveryRatingsForEvent(eventId: string, organizerSpaceTok
6566
}
6667

6768
export async function getTalksDetailsWithRatings(eventId: string) {
68-
const organizerSpaceRef = await getSecretTokenRef(`/events/${eventId}/organizer-space`)
69-
70-
const [ talks, everyRatings ] = await Promise.all([
71-
getTalkDetails(eventId),
72-
getEveryRatingsForEvent(eventId, organizerSpaceRef.id)
73-
]);
74-
75-
return talks.map(talk => ({
76-
talk,
77-
ratings: everyRatings.ratingsForTalk(talk.id)
78-
}))
69+
return logPerf(`getTalksDetailsWithRatings(${eventId})`, async () => {
70+
const organizerSpaceRef = await getSecretTokenRef(`/events/${eventId}/organizer-space`)
71+
72+
const [ talks, everyRatings ] = await Promise.all([
73+
getTalkDetails(eventId),
74+
getEveryRatingsForEvent(eventId, organizerSpaceRef.id)
75+
]);
76+
77+
return talks.map(talk => ({
78+
talk,
79+
ratings: everyRatings.ratingsForTalk(talk.id)
80+
}))
81+
})
7982
}

cloud/functions/src/functions/http/event/publicEventStats.ts

Lines changed: 106 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as functions from "firebase-functions";
2-
import {extractSingleQueryParam, roundedAverage, sendResponseMessage} from "../utils";
2+
import {extractSingleQueryParam, logPerf, roundedAverage, sendResponseMessage} from "../utils";
33
import {
44
checkEventLastUpdate,
55
eventTalkStatsFor
@@ -21,114 +21,124 @@ const publicEventStats = functions.https.onRequest(async (request, response) =>
2121
if(!eventId) { return sendResponseMessage(response, 400, `Missing [eventId] query parameter !`) }
2222
if(!publicToken) { return sendResponseMessage(response, 400, `Missing [publicToken] query parameter !`) }
2323

24-
const [eventDescriptor, familyEventsStatsToken] = await Promise.all([
25-
getEventDescriptor(eventId),
26-
getFamilyEventsStatsToken(publicToken),
27-
]);
24+
const [eventDescriptor, familyEventsStatsToken] = await logPerf("eventDescriptor and familyEventsStatsToken retrieval", async () => {
25+
return await Promise.all([
26+
getEventDescriptor(eventId),
27+
getFamilyEventsStatsToken(publicToken),
28+
]);
29+
})
2830

2931
if(!eventDescriptor.eventFamily || !familyEventsStatsToken.eventFamilies.includes(eventDescriptor.eventFamily)) {
3032
return sendResponseMessage(response, 400, `Provided family events stats token doesn't match with event ${eventId} family: [${eventDescriptor.eventFamily}]`)
3133
}
3234

33-
const { cachedHash, updatesDetected } = await checkEventLastUpdate(eventId, [
34-
root => root.favorites,
35-
root => root.allFeedbacks,
36-
root => root.talkListUpdated
37-
], request, response)
35+
const { cachedHash, updatesDetected } = await logPerf("cached hash", async () => {
36+
return await checkEventLastUpdate(eventId, [
37+
root => root.favorites,
38+
root => root.allFeedbacks,
39+
root => root.talkListUpdated
40+
], request, response)
41+
});
42+
3843
if(!updatesDetected) {
3944
return sendResponseMessage(response, 304)
4045
}
4146

4247
try {
43-
const [talkStats, talksDetailsWithRatings] = await Promise.all([
44-
eventTalkStatsFor(eventId),
45-
getTalksDetailsWithRatings(eventId),
46-
])
47-
48-
const perTalkStats = talksDetailsWithRatings.map(talkDetails => ({
49-
talkId: talkDetails.talk.id,
50-
talkTitle: talkDetails.talk.title,
51-
totalFavoritesCount: talkStats.find(ts => ts.id === talkDetails.talk.id)?.totalFavoritesCount || 0
52-
}))
53-
54-
type DailyTalksAndRatings = {
55-
dayId: string,
56-
date: ISOLocalDate,
57-
talks: typeof talksDetailsWithRatings
58-
}
59-
const dailyTalks = talksDetailsWithRatings.reduce((dailyTalks, talkAndRatings) => {
60-
let talkLocalDate = talkAndRatings.talk.start.substring(0, "yyyy-mm-dd".length) as ISOLocalDate;
61-
const day = match(dailyTalks.find(dt => dt.date === talkLocalDate))
62-
.with(P.nullish, (_) => {
63-
const day: DailyTalksAndRatings = {
64-
dayId: eventDescriptor.days.find(d => d.localDate === talkLocalDate)!.id,
65-
date: talkLocalDate,
66-
talks: []
67-
}
68-
dailyTalks.push(day);
69-
return day;
70-
}).otherwise(day => day);
71-
72-
day.talks.push(talkAndRatings)
73-
74-
return dailyTalks;
75-
}, [] as DailyTalksAndRatings[])
76-
77-
const eventTopRatedTalksConfig = eventDescriptor.features.topRatedTalks || {
78-
numberOfDailyTopTalksConsidered: 10,
79-
minimumAverageScoreToBeConsidered: Math.floor((eventDescriptor.features.ratings.scale.labels.length + 1) / 2),
80-
minimumNumberOfRatingsToBeConsidered: 10
81-
}
82-
83-
if(!eventDescriptor.features.ratings.scale.enabled) {
84-
eventTopRatedTalksConfig.minimumNumberOfRatingsToBeConsidered = Infinity;
85-
}
86-
87-
const dailyTalksStats = sortBy(dailyTalks, dt => dt.date)
88-
.map(dt => {
89-
const talksWithValidAverageRating = dt.talks.map(talk => {
90-
const nonNullishRatings = talk.ratings
91-
.map(r => r['linear-rating'])
92-
.filter(v => v !== null && v !== undefined) as number[];
48+
const [talkStats, talksDetailsWithRatings] = await logPerf("eventTalkStats + getTalksDetailsWithRatings", () => {
49+
return Promise.all([
50+
eventTalkStatsFor(eventId),
51+
getTalksDetailsWithRatings(eventId),
52+
]);
53+
})
54+
55+
const {perTalkStats, dailyTalksStats} = await logPerf("Post processing", async () => {
56+
const perTalkStats = talksDetailsWithRatings.map(talkDetails => ({
57+
talkId: talkDetails.talk.id,
58+
talkTitle: talkDetails.talk.title,
59+
totalFavoritesCount: talkStats.find(ts => ts.id === talkDetails.talk.id)?.totalFavoritesCount || 0
60+
}))
61+
62+
type DailyTalksAndRatings = {
63+
dayId: string,
64+
date: ISOLocalDate,
65+
talks: typeof talksDetailsWithRatings
66+
}
67+
const dailyTalks = talksDetailsWithRatings.reduce((dailyTalks, talkAndRatings) => {
68+
let talkLocalDate = talkAndRatings.talk.start.substring(0, "yyyy-mm-dd".length) as ISOLocalDate;
69+
const day = match(dailyTalks.find(dt => dt.date === talkLocalDate))
70+
.with(P.nullish, (_) => {
71+
const day: DailyTalksAndRatings = {
72+
dayId: eventDescriptor.days.find(d => d.localDate === talkLocalDate)!.id,
73+
date: talkLocalDate,
74+
talks: []
75+
}
76+
dailyTalks.push(day);
77+
return day;
78+
}).otherwise(day => day);
79+
80+
day.talks.push(talkAndRatings)
81+
82+
return dailyTalks;
83+
}, [] as DailyTalksAndRatings[])
84+
85+
const eventTopRatedTalksConfig = eventDescriptor.features.topRatedTalks || {
86+
numberOfDailyTopTalksConsidered: 10,
87+
minimumAverageScoreToBeConsidered: Math.floor((eventDescriptor.features.ratings.scale.labels.length + 1) / 2),
88+
minimumNumberOfRatingsToBeConsidered: 10
89+
}
90+
91+
if(!eventDescriptor.features.ratings.scale.enabled) {
92+
eventTopRatedTalksConfig.minimumNumberOfRatingsToBeConsidered = Infinity;
93+
}
94+
95+
const dailyTalksStats = sortBy(dailyTalks, dt => dt.date)
96+
.map(dt => {
97+
const talksWithValidAverageRating = dt.talks.map(talk => {
98+
const nonNullishRatings = talk.ratings
99+
.map(r => r['linear-rating'])
100+
.filter(v => v !== null && v !== undefined) as number[];
101+
102+
return {
103+
...talk.talk,
104+
averageRating: (nonNullishRatings.length >= eventTopRatedTalksConfig.minimumNumberOfRatingsToBeConsidered)
105+
? roundedAverage(nonNullishRatings)
106+
: undefined,
107+
numberOfVotes: nonNullishRatings.length
108+
};
109+
}).filter(t => t.averageRating !== undefined
110+
&& (eventTopRatedTalksConfig.minimumAverageScoreToBeConsidered === undefined || t.averageRating >= eventTopRatedTalksConfig.minimumAverageScoreToBeConsidered)
111+
);
112+
113+
const topTalks = sortBy(talksWithValidAverageRating, t => -t.averageRating!)
114+
.filter((talk, index) => index < eventTopRatedTalksConfig.numberOfDailyTopTalksConsidered)
115+
.map(talk => ({
116+
talkId: talk.id,
117+
title: talk.title,
118+
speakers: talk.speakers.map(s => ({
119+
id: s.id,
120+
fullName: s.fullName,
121+
companyName: s.companyName,
122+
photoUrl: s.photoUrl
123+
})),
124+
start: talk.start, end: talk.end,
125+
format: talk.format.title,
126+
language: talk.language,
127+
room: talk.room.title, track: talk.track.title,
128+
tags: talk.tags,
129+
averageRating: talk.averageRating,
130+
numberOfVotes: talk.numberOfVotes
131+
}))
93132

94133
return {
95-
...talk.talk,
96-
averageRating: (nonNullishRatings.length >= eventTopRatedTalksConfig.minimumNumberOfRatingsToBeConsidered)
97-
? roundedAverage(nonNullishRatings)
98-
: undefined,
99-
numberOfVotes: nonNullishRatings.length
100-
};
101-
}).filter(t => t.averageRating !== undefined
102-
&& (eventTopRatedTalksConfig.minimumAverageScoreToBeConsidered === undefined || t.averageRating >= eventTopRatedTalksConfig.minimumAverageScoreToBeConsidered)
103-
);
104-
105-
const topTalks = sortBy(talksWithValidAverageRating, t => -t.averageRating!)
106-
.filter((talk, index) => index < eventTopRatedTalksConfig.numberOfDailyTopTalksConsidered)
107-
.map(talk => ({
108-
talkId: talk.id,
109-
title: talk.title,
110-
speakers: talk.speakers.map(s => ({
111-
id: s.id,
112-
fullName: s.fullName,
113-
companyName: s.companyName,
114-
photoUrl: s.photoUrl
115-
})),
116-
start: talk.start, end: talk.end,
117-
format: talk.format.title,
118-
language: talk.language,
119-
room: talk.room.title, track: talk.track.title,
120-
tags: talk.tags,
121-
averageRating: talk.averageRating,
122-
numberOfVotes: talk.numberOfVotes
123-
}))
124-
125-
return {
126-
date: dt.date,
127-
dayId: dt.dayId,
128-
topTalks
129-
}
130-
});
134+
date: dt.date,
135+
dayId: dt.dayId,
136+
topTalks
137+
}
138+
});
131139

140+
return { perTalkStats, dailyTalksStats }
141+
})
132142

133143
sendResponseMessage(response, 200, {
134144
perTalkStats,

cloud/functions/src/functions/http/utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,13 @@ export function sendResponseMessage(response: Response, httpCode: number, messag
3434
export function roundedAverage(values: number[]) {
3535
return Math.round(values.reduce((sum, v) => sum+v, 0) * 100 / values.length) / 100;
3636
}
37+
38+
export async function logPerf<T>(message: string, callback: () => Promise<T>) {
39+
const start = Date.now();
40+
41+
console.log(`START: ${message}`)
42+
const results = await callback()
43+
console.log(`END: ${message} -- Elapsed: ${Date.now()-start}ms`)
44+
45+
return results;
46+
}

0 commit comments

Comments
 (0)