Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WebGL escape hatch #5494

Merged
merged 9 commits into from Jan 25, 2022
Merged

Conversation

JamesHollyer
Copy link
Contributor

While we hope our error catching is sufficient to automate the fallback to SVG when WebGL fails we still want to give users the ability to force the use of SVG. This is not something we want most users to do. This should only be used if WebGL is failing and out fallback is not working.

This CL adds a secret query parameter that will allow users in the know to force the use of SVG. It also adds a secret parameter to stop forcing SVG and return to the default behavior.

@bmd3k
Copy link
Contributor

bmd3k commented Jan 11, 2022

Ultimately, like all state in our Angular app, the source of truth for the user's decision to use WebGL vs SVG should be the in-memory ngrx state. On app load we would construct the ngrx-specific value taking into consideration the value from the query parameters and from local settings. Normally we would represent this sort of state as a "feature flag":

on(globalSettingsLoaded, (state, {partialSettings}) => {

That line, specifically, shows how we read "feature flags" from "persistent settings", which is the abstraction on top of local storage:

https://github.com/tensorflow/tensorboard/tree/master/tensorboard/webapp/persistent_settings

This is where we read some feature flag overrides from query parameters:

https://github.com/tensorflow/tensorboard/blob/367839f30de3968eff5fecb8b38e1d5782434b6b/tensorboard/webapp/webapp_data_source/tb_feature_flag_data_source.ts

And, finally, I believe this is an example of how you could take the value from the ngrx state and update the local storage via (via persistent settings):

export function getThemeSettingSelector() {

Copy link
Contributor

@bmd3k bmd3k left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ForceSVGDataSource is missing from these changes. Can you add it?

@bmd3k bmd3k self-requested a review January 19, 2022 19:26
@@ -33,6 +34,7 @@ export class FeatureFlagEffects {
combineLatestWith(this.store.select(getIsAutoDarkModeAllowed)),
map(([, isDarkModeAllowed]) => {
const features = this.dataSource.getFeatures(isDarkModeAllowed);
features.forceSVG = this.ForceSVGDataSource.getAndUpdateForceSVGFlag();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data source layer is ideally sort of dumb and just reads/writes data without trying to make any decisions. The decision making (aka business logic) is ideally in the effects and reducers layers.

My recommendation:

  1. Rely on TBFeatureFlagDataSource.getFeatures() to read the value of the forceSVG query parameter. If it follows the pattern of other query parameters then it should return empty value for forceSVG if it is not specified in the URL.

  2. Introduce ForceSVGDataSource.getForceSVG() that returns the value from local storage or null if there is none.

  3. Write logic in the Effects layer to determine the actual value of ForceSVG based on (1) and (2). The value is possibly empty (in which case, in practice, the app will rely on the default in the ngrx state). Include this value in the partialFeatureFlagsLoaded.

  4. Introduce ForceSVGDataSoruce.updateForceSVG() to write the value to local storage, if appropriate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all sounds good. However, if we are only pulling from localStorage for the ForceSVGDataSource it does not need all the overhead of an injectable class. I am going to change it to force_svg_util and just have a getForceSvg and updateForceSvg functions as standalone functions. Does that sound good to you?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this particular case, I can empathise with the temptation to avoid using Angular's Dependency Injection (DI). Why you might think this way:

  1. force_svg_util itself currently has no DI-able dependencies (although I think it's hard to be certain that will be true forever)
  2. we don't foresee wanting to to DI some other implementation of ForceSVGDataSource -- we think it will always be a local storage thing, unlike persistent settings which might one day be stored on the server.

Having said that, I think it's good habit/hygiene to make this a DI-able object like any other object in our system that reads/writes to a data source. I agree there is some overhead but it is pretty standard Angular stuff. It will also give you standard options/patterns when you write tests - how to Mock, how to Fake/Stub.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it injectable again.

I also added tests.

PTAL.

tensorboard/webapp/webapp_data_source/BUILD Outdated Show resolved Hide resolved
@@ -91,6 +91,7 @@ tf_ng_module(
tf_ng_module(
name = "feature_flag",
srcs = [
"force_SVG_data_source.ts",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Over time we've been trying to move things out of webapp_data_source and into feature-specific folders. Ideally force_svg_data_source and tb_feature_flag_data_source and tb_feature_flag_module would be in the feature_flag folder.

Could we put force_svg_data_source there immediately?

Would you be interested in following up with cleanup PRs to move tb_feature_flag_data_source and tb_feature_flag_module there as well? (disclosure: this may be a bit difficult because of the fact that classes in our internal repo reference these classes)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved and renamed. Yea I can probably do that cleanup. I will look into what it will take to make that happen. Maybe make a copy of it temporarily until we can switch internal code over then remove.

@@ -90,6 +91,10 @@ export class QueryParamsFeatureFlagDataSource
params.get(ENABLE_TIME_NAMESPACED_STATE) !== 'false';
}

if (params.has(FORCE_SVG_RENDERER)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add tests for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

@@ -102,7 +102,7 @@
></mat-spinner>
<line-chart
[disableUpdate]="!isCardVisible"
[preferredRendererType]="RendererType.SVG"
[preferredRendererType]="forceSvg ? RendererType.SVG : RendererType.WEBGL"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add tests for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

@@ -50,4 +50,6 @@ export interface FeatureFlags {
// Whether to enable time-namespaced state and how it impacts how user
// settings are kept during navigation.
enabledTimeNamespacedState: boolean;

forceSvg: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document? Worth mentioning this is for TimeSeries Scalars cards only (I think?).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

import {FeatureFlagEffects} from './feature_flag_effects';

describe('feature_flag_effects', () => {
fdescribe('feature_flag_effects', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dangling fdescribe here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doh! removed.

@@ -0,0 +1,22 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2022

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doh!

removeItemSpy = spyOn(window.localStorage, 'removeItem').and.stub();
dataSource = TestBed.inject(ForceSvgDataSource);
});
describe('#getForceSVG', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Newline before this line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

limitations under the License.
==============================================================================*/
import {Injectable} from '@angular/core';
import {FORCE_SVG_RENDERER} from '../webapp_data_source/tb_feature_flag_data_source_types';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than sharing the query parameter key perhaps we should use a name more like the one used by PersistentSettingsDataSource:

https://cs.opensource.google/tensorflow/tensorboard/+/master:tensorboard/webapp/persistent_settings/_data_source/persistent_settings_data_source.ts;l=22;drc=b0f27571ce3e6ce322b9f1b91a4e8773f9ca47e0

Prefixed with '_tb_'.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes more sense. Changed.

});

describe('updateForceSVG', () => {
it('Creates localStorage entry with key forceSVG when passed truthy', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit 'Creates' => 'creates'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


describe('updateForceSVG', () => {
it('Creates localStorage entry with key forceSVG when passed truthy', () => {
let dataSource = new ForceSvgDataSource();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use dataSource you get from the TestBed instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not mean to leave this line in there. Removed.

);
});
it('calls localStorage.removeItem with key forceSVG', () => {
let dataSource = new ForceSvgDataSource();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use dataSource you get from the TestBed instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed.

Copy link
Contributor Author

@JamesHollyer JamesHollyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Brian. Let me know if there is anything else you can see to improve this.

import {FeatureFlagEffects} from './feature_flag_effects';

describe('feature_flag_effects', () => {
fdescribe('feature_flag_effects', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doh! removed.

expect(updateSpy).toHaveBeenCalledOnceWith(true);
});

fit('gets forceSVG flag from ForceSvgDataSource when getFeatures returns no value for forceSvg', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed.

expect(updateSpy).toHaveBeenCalledOnceWith(true);
});

fit('gets forceSVG flag from ForceSvgDataSource when getFeatures returns no value for forceSvg', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.


@Injectable()
export class ForceSvgDataSource {
constructor() {}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love to do that. I see it as a useless abstraction. I am not a fan of useless abstractions. The basic localStorage API works great by itself(I might be a bit biased because my last team owned that API :P)

@@ -0,0 +1,22 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doh!

@@ -50,4 +50,6 @@ export interface FeatureFlags {
// Whether to enable time-namespaced state and how it impacts how user
// settings are kept during navigation.
enabledTimeNamespacedState: boolean;

forceSvg: boolean;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

@@ -102,7 +102,7 @@
></mat-spinner>
<line-chart
[disableUpdate]="!isCardVisible"
[preferredRendererType]="RendererType.SVG"
[preferredRendererType]="forceSvg ? RendererType.SVG : RendererType.WEBGL"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

@@ -90,6 +91,10 @@ export class QueryParamsFeatureFlagDataSource
params.get(ENABLE_TIME_NAMESPACED_STATE) !== 'false';
}

if (params.has(FORCE_SVG_RENDERER)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.


updateForceSvgFlag(forceSvgFlag: boolean) {
if (forceSvgFlag) {
localStorage.setItem(FORCE_SVG_RENDERER, 'true');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that. Changed.

limitations under the License.
==============================================================================*/
import {Injectable} from '@angular/core';
import {FORCE_SVG_RENDERER} from '../webapp_data_source/tb_feature_flag_data_source_types';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes more sense. Changed.

@@ -50,6 +50,7 @@ export interface FeatureFlags {
// Whether to enable time-namespaced state and how it impacts how user
// settings are kept during navigation.
enabledTimeNamespacedState: boolean;

// Flag for the escape hatch from webGL. This only effects the TimeSeries
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: webGL => WebGL

@JamesHollyer JamesHollyer merged commit 7a2a169 into tensorflow:master Jan 25, 2022
yatbear pushed a commit to yatbear/tensorboard that referenced this pull request Mar 27, 2023
There is logic to fallback to SVG if we receive an error from WebGL. However, if some users are having trouble with WebGL and we are not catching the error we wanted an escape hatch for users to force the use of SVG.

This PR adds that escape hatch as a hidden query parameter that users can add. Once added it will be stored locally until turned off which can be done with another query parameter.
dna2github pushed a commit to dna2fork/tensorboard that referenced this pull request May 1, 2023
There is logic to fallback to SVG if we receive an error from WebGL. However, if some users are having trouble with WebGL and we are not catching the error we wanted an escape hatch for users to force the use of SVG.

This PR adds that escape hatch as a hidden query parameter that users can add. Once added it will be stored locally until turned off which can be done with another query parameter.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants