This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
242 lines
7.4 KiB
242 lines
7.4 KiB
/** |
|
* Copyright (c) 2015-present, Facebook, Inc. |
|
* |
|
* This source code is licensed under the MIT license found in the |
|
* LICENSE file in the root directory of this source tree. |
|
* |
|
* @providesModule FillRateHelper |
|
* @flow |
|
* @format |
|
*/ |
|
|
|
'use strict'; |
|
|
|
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error |
|
* found when Flow v0.54 was deployed. To see the error delete this comment and |
|
* run Flow. */ |
|
const performanceNow = require('fbjs/lib/performanceNow'); |
|
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error |
|
* found when Flow v0.54 was deployed. To see the error delete this comment and |
|
* run Flow. */ |
|
const warning = require('fbjs/lib/warning'); |
|
|
|
export type FillRateInfo = Info; |
|
|
|
class Info { |
|
any_blank_count = 0; |
|
any_blank_ms = 0; |
|
any_blank_speed_sum = 0; |
|
mostly_blank_count = 0; |
|
mostly_blank_ms = 0; |
|
pixels_blank = 0; |
|
pixels_sampled = 0; |
|
pixels_scrolled = 0; |
|
total_time_spent = 0; |
|
sample_count = 0; |
|
} |
|
|
|
type FrameMetrics = {inLayout?: boolean, length: number, offset: number}; |
|
|
|
const DEBUG = false; |
|
|
|
let _listeners: Array<(Info) => void> = []; |
|
let _minSampleCount = 10; |
|
let _sampleRate = DEBUG ? 1 : null; |
|
|
|
/** |
|
* A helper class for detecting when the maximem fill rate of `VirtualizedList` is exceeded. |
|
* By default the sampling rate is set to zero and this will do nothing. If you want to collect |
|
* samples (e.g. to log them), make sure to call `FillRateHelper.setSampleRate(0.0-1.0)`. |
|
* |
|
* Listeners and sample rate are global for all `VirtualizedList`s - typical usage will combine with |
|
* `SceneTracker.getActiveScene` to determine the context of the events. |
|
*/ |
|
class FillRateHelper { |
|
_anyBlankStartTime = (null: ?number); |
|
_enabled = false; |
|
_getFrameMetrics: (index: number) => ?FrameMetrics; |
|
_info = new Info(); |
|
_mostlyBlankStartTime = (null: ?number); |
|
_samplesStartTime = (null: ?number); |
|
|
|
static addListener(callback: FillRateInfo => void): {remove: () => void} { |
|
warning( |
|
_sampleRate !== null, |
|
'Call `FillRateHelper.setSampleRate` before `addListener`.', |
|
); |
|
_listeners.push(callback); |
|
return { |
|
remove: () => { |
|
_listeners = _listeners.filter(listener => callback !== listener); |
|
}, |
|
}; |
|
} |
|
|
|
static setSampleRate(sampleRate: number) { |
|
_sampleRate = sampleRate; |
|
} |
|
|
|
static setMinSampleCount(minSampleCount: number) { |
|
_minSampleCount = minSampleCount; |
|
} |
|
|
|
constructor(getFrameMetrics: (index: number) => ?FrameMetrics) { |
|
this._getFrameMetrics = getFrameMetrics; |
|
this._enabled = (_sampleRate || 0) > Math.random(); |
|
this._resetData(); |
|
} |
|
|
|
activate() { |
|
if (this._enabled && this._samplesStartTime == null) { |
|
DEBUG && console.debug('FillRateHelper: activate'); |
|
this._samplesStartTime = performanceNow(); |
|
} |
|
} |
|
|
|
deactivateAndFlush() { |
|
if (!this._enabled) { |
|
return; |
|
} |
|
const start = this._samplesStartTime; // const for flow |
|
if (start == null) { |
|
DEBUG && |
|
console.debug('FillRateHelper: bail on deactivate with no start time'); |
|
return; |
|
} |
|
if (this._info.sample_count < _minSampleCount) { |
|
// Don't bother with under-sampled events. |
|
this._resetData(); |
|
return; |
|
} |
|
const total_time_spent = performanceNow() - start; |
|
const info: any = { |
|
...this._info, |
|
total_time_spent, |
|
}; |
|
if (DEBUG) { |
|
const derived = { |
|
avg_blankness: this._info.pixels_blank / this._info.pixels_sampled, |
|
avg_speed: this._info.pixels_scrolled / (total_time_spent / 1000), |
|
avg_speed_when_any_blank: |
|
this._info.any_blank_speed_sum / this._info.any_blank_count, |
|
any_blank_per_min: |
|
this._info.any_blank_count / (total_time_spent / 1000 / 60), |
|
any_blank_time_frac: this._info.any_blank_ms / total_time_spent, |
|
mostly_blank_per_min: |
|
this._info.mostly_blank_count / (total_time_spent / 1000 / 60), |
|
mostly_blank_time_frac: this._info.mostly_blank_ms / total_time_spent, |
|
}; |
|
for (const key in derived) { |
|
derived[key] = Math.round(1000 * derived[key]) / 1000; |
|
} |
|
console.debug('FillRateHelper deactivateAndFlush: ', {derived, info}); |
|
} |
|
_listeners.forEach(listener => listener(info)); |
|
this._resetData(); |
|
} |
|
|
|
computeBlankness( |
|
props: { |
|
data: Array<any>, |
|
getItemCount: (data: Array<any>) => number, |
|
initialNumToRender: number, |
|
}, |
|
state: { |
|
first: number, |
|
last: number, |
|
}, |
|
scrollMetrics: { |
|
dOffset: number, |
|
offset: number, |
|
velocity: number, |
|
visibleLength: number, |
|
}, |
|
): number { |
|
if ( |
|
!this._enabled || |
|
props.getItemCount(props.data) === 0 || |
|
this._samplesStartTime == null |
|
) { |
|
return 0; |
|
} |
|
const {dOffset, offset, velocity, visibleLength} = scrollMetrics; |
|
|
|
// Denominator metrics that we track for all events - most of the time there is no blankness and |
|
// we want to capture that. |
|
this._info.sample_count++; |
|
this._info.pixels_sampled += Math.round(visibleLength); |
|
this._info.pixels_scrolled += Math.round(Math.abs(dOffset)); |
|
const scrollSpeed = Math.round(Math.abs(velocity) * 1000); // px / sec |
|
|
|
// Whether blank now or not, record the elapsed time blank if we were blank last time. |
|
const now = performanceNow(); |
|
if (this._anyBlankStartTime != null) { |
|
this._info.any_blank_ms += now - this._anyBlankStartTime; |
|
} |
|
this._anyBlankStartTime = null; |
|
if (this._mostlyBlankStartTime != null) { |
|
this._info.mostly_blank_ms += now - this._mostlyBlankStartTime; |
|
} |
|
this._mostlyBlankStartTime = null; |
|
|
|
let blankTop = 0; |
|
let first = state.first; |
|
let firstFrame = this._getFrameMetrics(first); |
|
while (first <= state.last && (!firstFrame || !firstFrame.inLayout)) { |
|
firstFrame = this._getFrameMetrics(first); |
|
first++; |
|
} |
|
// Only count blankTop if we aren't rendering the first item, otherwise we will count the header |
|
// as blank. |
|
if (firstFrame && first > 0) { |
|
blankTop = Math.min( |
|
visibleLength, |
|
Math.max(0, firstFrame.offset - offset), |
|
); |
|
} |
|
let blankBottom = 0; |
|
let last = state.last; |
|
let lastFrame = this._getFrameMetrics(last); |
|
while (last >= state.first && (!lastFrame || !lastFrame.inLayout)) { |
|
lastFrame = this._getFrameMetrics(last); |
|
last--; |
|
} |
|
// Only count blankBottom if we aren't rendering the last item, otherwise we will count the |
|
// footer as blank. |
|
if (lastFrame && last < props.getItemCount(props.data) - 1) { |
|
const bottomEdge = lastFrame.offset + lastFrame.length; |
|
blankBottom = Math.min( |
|
visibleLength, |
|
Math.max(0, offset + visibleLength - bottomEdge), |
|
); |
|
} |
|
const pixels_blank = Math.round(blankTop + blankBottom); |
|
const blankness = pixels_blank / visibleLength; |
|
if (blankness > 0) { |
|
this._anyBlankStartTime = now; |
|
this._info.any_blank_speed_sum += scrollSpeed; |
|
this._info.any_blank_count++; |
|
this._info.pixels_blank += pixels_blank; |
|
if (blankness > 0.5) { |
|
this._mostlyBlankStartTime = now; |
|
this._info.mostly_blank_count++; |
|
} |
|
} else if (scrollSpeed < 0.01 || Math.abs(dOffset) < 1) { |
|
this.deactivateAndFlush(); |
|
} |
|
return blankness; |
|
} |
|
|
|
enabled(): boolean { |
|
return this._enabled; |
|
} |
|
|
|
_resetData() { |
|
this._anyBlankStartTime = null; |
|
this._info = new Info(); |
|
this._mostlyBlankStartTime = null; |
|
this._samplesStartTime = null; |
|
} |
|
} |
|
|
|
module.exports = FillRateHelper;
|
|
|