Skip to content

Commit fb21e38

Browse files
committed
add stats of compile time / fps sent to server
1 parent 1a08b7d commit fb21e38

File tree

4 files changed

+143
-1
lines changed

4 files changed

+143
-1
lines changed

server/index.js

+94
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const promisify = (f, ctx = null) => (...args) =>
1313
const connectMongo = promisify(MongoClient.connect);
1414
const scores = () =>
1515
connectMongo(env.MONGO).then(db => db.collection("scores"));
16+
const stats = () => connectMongo(env.MONGO).then(db => db.collection("stats"));
1617

1718
scores().then(coll => promisify(coll.count, coll)()).then(count => {
1819
console.log(count + " scores");
@@ -46,6 +47,17 @@ const recordScore = doc =>
4647
);
4748
});
4849

50+
const fetchStats = () =>
51+
stats().then(coll => {
52+
const q = coll.find();
53+
return promisify(q.toArray, q)();
54+
});
55+
56+
const recordStats = doc =>
57+
stats().then(coll => {
58+
return promisify(coll.insertOne, coll)(doc);
59+
});
60+
4961
const app = require("express")();
5062
app.use(require("body-parser").json());
5163
app.use(require("cors")());
@@ -81,6 +93,88 @@ app.post("/test", (req, res) => {
8193
}
8294
});
8395

96+
function checkValidStat(body) {
97+
if (!body || typeof body !== "object") {
98+
throw new Error("invalid body");
99+
}
100+
if (typeof body.userAgent !== "string") {
101+
throw new Error("invalid body.userAgent");
102+
}
103+
if (
104+
typeof body.config !== "object" ||
105+
typeof body.config.quality !== "string" ||
106+
typeof body.config.seed !== "string"
107+
) {
108+
throw new Error("invalid body.config");
109+
}
110+
}
111+
function checkValidSuccessStat(body) {
112+
if (!body.stats || typeof body.stats !== "object")
113+
throw new Error("missing body.stats");
114+
if (typeof body.stats.averageFPS !== "number")
115+
throw new Error("invalid body.stats.averageFPS");
116+
if (typeof body.stats.bootTime !== "number")
117+
throw new Error("invalid body.stats.bootTime");
118+
}
119+
function checkValidFailureStat(body) {
120+
if (typeof body.error !== "string") throw new Error("missing body.error");
121+
}
122+
123+
app.get("/stats", (req, res) => {
124+
fetchStats()
125+
.then(stats => {
126+
const successEntries = stats.filter(s => s.type === "success");
127+
const failureEntries = stats.filter(s => s.type === "failure");
128+
const out = {
129+
successCount: successEntries.length,
130+
failureCount: failureEntries.length,
131+
statsPerQuality: {
132+
low: successEntries
133+
.filter(s => s.config.quality === "low")
134+
.map(s => s.stats),
135+
medium: successEntries
136+
.filter(s => s.config.quality === "medium")
137+
.map(s => s.stats),
138+
high: successEntries
139+
.filter(s => s.config.quality === "high")
140+
.map(s => s.stats)
141+
}
142+
};
143+
res.json(out);
144+
})
145+
.catch(e => {
146+
console.error(e);
147+
res.status(500).send();
148+
});
149+
});
150+
151+
app.post("/stats/failure", (req, res) => {
152+
Promise.resolve(req.body)
153+
.then(body => {
154+
checkValidStat(body);
155+
checkValidFailureStat(body);
156+
return recordStats(Object.assign({ type: "failure" }, body));
157+
})
158+
.then(res.send())
159+
.catch(e => {
160+
console.log(e);
161+
res.status(500).send();
162+
});
163+
});
164+
app.post("/stats/success", (req, res) => {
165+
Promise.resolve(req.body)
166+
.then(body => {
167+
checkValidStat(body);
168+
checkValidSuccessStat(body);
169+
return recordStats(Object.assign({ type: "success" }, body));
170+
})
171+
.then(res.send())
172+
.catch(e => {
173+
console.log(e);
174+
res.status(500).send();
175+
});
176+
});
177+
84178
app.post("/", (req, res) => {
85179
// a GameState is sent to server
86180
Promise.resolve(req.body)

src/Game/Render.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ class Game extends Component {
184184
componentDidMount() {
185185
const { body } = document;
186186
const { canvas } = this;
187-
const { onGLFailure } = this.props;
187+
const { onGLFailure, onSuccessStart } = this.props;
188188
if (!body || !canvas) return;
189189
const { getGameState, action } = this.props;
190190
for (let k = 0; k < 500; k++) {
@@ -326,11 +326,29 @@ class Game extends Component {
326326
});
327327

328328
let lastTime;
329+
let bootTime;
330+
let timeAtTick100;
331+
let timeAtTick100After10sReached = false;
329332

330333
regl.frame(e => {
331334
const prevState = getGameState();
332335
const state = action("tick", e, this.getUserEvents());
333336

337+
if (e.tick === 30) {
338+
bootTime = Math.max(0, e.time - 0.5); // 30 ticks assumed to be 0.5s
339+
} else if (e.tick === 100) {
340+
timeAtTick100 = e.time;
341+
} else if (
342+
!timeAtTick100After10sReached &&
343+
e.tick > 100 &&
344+
e.time - timeAtTick100 >= 10
345+
) {
346+
// 10 seconds after tick 100
347+
timeAtTick100After10sReached = true;
348+
const averageFPS = (e.tick - 100) / 10;
349+
onSuccessStart({ bootTime, averageFPS });
350+
}
351+
334352
if (prevState.track !== state.track) {
335353
encodeTrack(state.track, trackData);
336354
track({

src/Game/Stats.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//@flow
2+
import env from "../env";
3+
import type { Config } from "./Config";
4+
5+
const getExtraParams = () => {
6+
return {
7+
userAgent: navigator.userAgent,
8+
href: window.location.href
9+
};
10+
};
11+
12+
export const sendSuccess = (config: Config, stats: *) =>
13+
fetch(env.highscoresAPI + "/stats/success", {
14+
method: "POST",
15+
headers: { "Content-Type": "application/json" },
16+
body: JSON.stringify({ ...getExtraParams(), config, stats })
17+
}).then(res => res.json());
18+
19+
export const sendFailure = (config: Config, error: string) =>
20+
fetch(env.highscoresAPI + "/stats/failure", {
21+
method: "POST",
22+
headers: { "Content-Type": "application/json" },
23+
body: JSON.stringify({ ...getExtraParams(), config, error })
24+
}).then(res => res.json());

src/Game/index.js

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import "./index.css";
66
import * as Conf from "./Config";
77
import type { Config } from "./Config";
88
import * as HighScores from "./HighScores";
9+
import * as Stats from "./Stats";
910
let Logic = require("./Logic").default;
1011
let { default: Render } = require("./Render");
1112

@@ -218,10 +219,14 @@ class GameComponent extends Component {
218219
});
219220
};
220221
onGLFailure = (errorMessage: string) => {
222+
Stats.sendFailure(this.state.config, errorMessage);
221223
this.gameState = null;
222224
this.forceUpdate();
223225
if (process.env.NODE_ENV === "production") alert(errorMessage);
224226
};
227+
onSuccessStart = (stats: *) => {
228+
Stats.sendSuccess(this.state.config, stats);
229+
};
225230
render() {
226231
const {
227232
config,
@@ -257,6 +262,7 @@ class GameComponent extends Component {
257262
action={this.action}
258263
gameContext={{ highscores, title: gameContextTitle }}
259264
onGLFailure={this.onGLFailure}
265+
onSuccessStart={this.onSuccessStart}
260266
/>
261267
);
262268
} else {

0 commit comments

Comments
 (0)