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

Adding support to filter columns for the Pod Metrics script #45

Merged
merged 22 commits into from
Jun 14, 2022

Conversation

kpattaswamy
Copy link
Member

Added a drop down select option for a user to choose what columns to filter in the Pods-Metrics table. Did this by adding support to dynamically replace part of the px script dealing with displaying all columns with only columns chosen by user.

Signed-off-by: Kartik Pattaswamy kpattaswamy@pixielabs.ai

Comment on lines 36 to 32
out = output[[` +
podColumnQueries[0] +
podColumnQueries[1] +
podColumnQueries[2] +
podColumnQueries[3] +
podColumnQueries[4] +
podColumnQueries[5] +
podColumnQueries[6] +
podColumnQueries[7] +
`]]
px.display(out)`;

//array of json objects containing every column name with its corresponding index for an array containing the string format query
export const podColumnOptions = [
{ label: 'pod', value: 0 },
{ label: 'cpu_usage', value: 1 },
{ label: 'total_disk_read_throughput', value: 2 },
{ label: 'total_disk_write_throughput', value: 3 },
{ label: 'container_count', value: 4 },
{ label: 'node', value: 5 },
{ label: 'start_time', value: 6 },
{ label: 'status', value: 7 },
];

Copy link

Choose a reason for hiding this comment

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

we should put these pod column options as a property of the JSON for each script. that way, we don't hard code a particular script into the UI - it's all configurable via JSON

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok I'll add them as a property of JSON for the script

Copy link
Contributor

@TarasPriadka TarasPriadka left a comment

Choose a reason for hiding this comment

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

A few stylistic and structural comments

* SPDX-License-Identifier: Apache-2.0
*/

const podMetrics = require('pxl_scripts/pods-metrics.json');
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to use the user script if it has the filtering line in it? I think it may be slightly annoying for users if filtering disregards all of the changes they made.

Also, a thought for the future, we can send columns which we want to filter and then remove them in the go backend instead of altering the script.

src/types.ts Outdated Show resolved Hide resolved
px.display(out)`;

//array of json objects containing every column name with its corresponding index for an array containing the string format query
export const podColumnOptions = [
Copy link
Contributor

Choose a reason for hiding this comment

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

You can store podColumnQueries in the value field of podColumnOptions instead of indicies. You'd need to change.

Also add type Array<SelectableValue<Type>> for this variable where Type is the type of the value. If you use store pod column options here, you'd use string.

{ label: 'status', value: 7 },
];

export const tabularScripts: string[] = ['Pod Metrics'];
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe add an optional field in the JSONs tabular = true so you don't have to hardcode the script names here in code.


export const tabularScripts: string[] = ['Pod Metrics'];

export function makeFilteringScript(obj: any[]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

obj is not a descriptive name for a variable. Also any[] is a very broad type, specify a tighter type instead. Specify a return type as well(ex.

function add(x: number, y: number): number {
  return x + y;
}

)

const podGetDataScript = podMetrics.script.substr(0, podMetrics.script.indexOf('px.display'));

//Contains the part of the full script which contains all columns
var filteredColumnScript = podAllColumnsScript;
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't use var since it is not scoped properly in JS. Either use let for mutable variables or const. Specify a proper type.

src/datasource.ts Outdated Show resolved Hide resolved
{
"name": "Pod Metrics",
"description": "",
"script": "import px\n\ndef process_stats_by_entity(start_time: int, entity: str):\n ''' Gets the windowed process stats (CPU, memory, etc) per node or pod.\n Args:\n @start_time Starting time of the data to examine.\n @entity: Either pod or node_name.\n '''\n # Window size to use on time_ column for bucketing.\n ns_per_s = 1000 * 1000 * 1000\n window_ns = px.DurationNanos(10 * ns_per_s)\n\n df = px.DataFrame(table='process_stats', start_time=start_time)\n df[entity] = df.ctx[entity]\n df.timestamp = px.bin(df.time_, window_ns)\n # First calculate CPU usage by process (UPID) in each k8s_object\n # over all windows.\n df = df.groupby([entity, 'upid', 'timestamp']).agg(\n rss=('rss_bytes', px.mean),\n vsize=('vsize_bytes', px.mean),\n # The fields below are counters, so we take the min and the max to subtract them.\n cpu_utime_ns_max=('cpu_utime_ns', px.max),\n cpu_utime_ns_min=('cpu_utime_ns', px.min),\n cpu_ktime_ns_max=('cpu_ktime_ns', px.max),\n cpu_ktime_ns_min=('cpu_ktime_ns', px.min),\n read_bytes_max=('read_bytes', px.max),\n read_bytes_min=('read_bytes', px.min),\n write_bytes_max=('write_bytes', px.max),\n write_bytes_min=('write_bytes', px.min),\n rchar_bytes_max=('rchar_bytes', px.max),\n rchar_bytes_min=('rchar_bytes', px.min),\n wchar_bytes_max=('wchar_bytes', px.max),\n wchar_bytes_min=('wchar_bytes', px.min),\n )\n # Next calculate cpu usage and memory stats per window.\n df.cpu_utime_ns = df.cpu_utime_ns_max - df.cpu_utime_ns_min\n df.cpu_ktime_ns = df.cpu_ktime_ns_max - df.cpu_ktime_ns_min\n df.read_bytes = df.read_bytes_max - df.read_bytes_min\n df.write_bytes = df.write_bytes_max - df.write_bytes_min\n df.rchar_bytes = df.rchar_bytes_max - df.rchar_bytes_min\n df.wchar_bytes = df.wchar_bytes_max - df.wchar_bytes_min\n # Sum by UPID.\n df = df.groupby([entity, 'timestamp']).agg(\n cpu_ktime_ns=('cpu_ktime_ns', px.sum),\n cpu_utime_ns=('cpu_utime_ns', px.sum),\n read_bytes=('read_bytes', px.sum),\n write_bytes=('write_bytes', px.sum),\n rchar_bytes=('rchar_bytes', px.sum),\n wchar_bytes=('wchar_bytes', px.sum),\n rss=('rss', px.sum),\n vsize=('vsize', px.sum),\n )\n df.actual_disk_read_throughput = df.read_bytes / window_ns\n df.actual_disk_write_throughput = df.write_bytes / window_ns\n df.total_disk_read_throughput = df.rchar_bytes / window_ns\n df.total_disk_write_throughput = df.wchar_bytes / window_ns\n # Now take the mean value over the various timestamps.\n df = df.groupby(entity).agg(\n cpu_ktime_ns=('cpu_ktime_ns', px.mean),\n cpu_utime_ns=('cpu_utime_ns', px.mean),\n actual_disk_read_throughput=('actual_disk_read_throughput', px.mean),\n actual_disk_write_throughput=('actual_disk_write_throughput', px.mean),\n total_disk_read_throughput=('total_disk_read_throughput', px.mean),\n total_disk_write_throughput=('total_disk_write_throughput', px.mean),\n avg_rss=('rss', px.mean),\n avg_vsize=('vsize', px.mean),\n )\n # Finally, calculate total (kernel + user time) percentage used over window.\n df.cpu_usage = px.Percent((df.cpu_ktime_ns + df.cpu_utime_ns) / window_ns)\n return df.drop(['cpu_ktime_ns', 'cpu_utime_ns'])\n\n''' A list of pods in `namespace`.\nArgs:\n@start_time: The timestamp of data to start at.\n@namespace: The name of the namespace to filter on.\n'''\ndf = px.DataFrame(table='process_stats', start_time=$__from)\ndf.pod = df.ctx['pod_name']\ndf.node = df.ctx['node_name']\ndf.container = df.ctx['container_name']\ndf = df.groupby(['pod', 'node', 'container']).agg()\ndf = df.groupby(['pod', 'node']).agg(container_count=('container', px.count))\ndf.start_time = px.pod_name_to_start_time(df.pod)\ndf.status = px.pod_name_to_status(df.pod)\n\n\nprocess_stats = process_stats_by_entity($__from, 'pod')\noutput = process_stats.merge(df, how='inner', left_on='pod', right_on='pod',\n suffixes=['', '_x'])\n\n\npx.display(output[['pod', 'cpu_usage', 'total_disk_read_throughput',\n 'total_disk_write_throughput', 'container_count',\n 'node', 'start_time', 'status']])"
Copy link
Contributor

Choose a reason for hiding this comment

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

Default scripts should have a short docstring documenting what the script does with a link to grafana documentation. Look at other default scripts for reference.

defaultValue={scriptOptions[0]}
/>

{tabularScripts.includes(query.scriptName) ? (
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd suggest adding a boolean field tabular to the query object instead of tracking tabular script names in a list.


filterPodColumns(obj: any[]) {
if (obj) {
const constFilteredScript = makeFilteringScript(obj);
Copy link
Contributor

Choose a reason for hiding this comment

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

Use variable name filteredScript, its already clear that it is constant from the const keyword

Signed-off-by: Kartik Pattaswamy <kpattaswamy@pixielabs.ai>
Signed-off-by: Kartik Pattaswamy <kpattaswamy@pixielabs.ai>
TarasPriadka and others added 6 commits June 9, 2022 11:05
…milliseconds.

Signed-off-by: Taras Priadka <tpriadka@pixielabs.ai>
Signed-off-by: Taras Priadka <tpriadka@pixielabs.ai>
Signed-off-by: Taras Priadka <tpriadka@pixielabs.ai>
Signed-off-by: Kartik Pattaswamy <kpattaswamy@pixielabs.ai>
Signed-off-by: Kartik Pattaswamy <kpattaswamy@pixielabs.ai>
defaultValue={scriptOptions[0]}
/>

{query.isTabular === true ? (
Copy link
Member

Choose a reason for hiding this comment

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

Nit: You can shorten this to query.isTabular ? ( ...

Copy link
Member

Choose a reason for hiding this comment

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

Since the other path is just an empty fragment, you can even just do query.isTabular && (...) and not have a ternary at all. React renders nothing at all if it's told to render a literal false, so a && b resolves to either rendering nothing or rendering b.

options={query.columnOptions}
onChange={this.filterColumns.bind(this)}
width={32}
inputId="multi-select-ops"
Copy link
Member

Choose a reason for hiding this comment

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

Let's rename the input ID to something more specific, like column-select.

value: scriptObject.script,
value: scriptObject,
isTabular: scriptObject.isTabular,
columnOptions: scriptObject.columnNames,
Copy link
Member

Choose a reason for hiding this comment

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

For the columnNames field in the json files, it seems redundant to specify the value since arrays are inherently ordered anyway.

I think restructuring the JSON file to just be:
"columnNames": ["timestamp", "remote_addr", ...]

Then here, we convert it to the values/label obj:
columnOptions: scriptObject.columnNames.map((name, index) => { 'label': name, 'value': index })


//Splits the script into two parts: 1. Script up till display columns, 2. Script after displaying columns
function splitScript(script: string): string[] {
const beforeDisplayCol = script.slice(0, script.indexOf('[['));
Copy link
Member

Choose a reason for hiding this comment

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

My worry here is if there's a script that uses [[ and ]] multiple times, and not just in the final output. For example, if you take a look at the px/cluster script's conn_stats_by_service function.

Luckily these scripts are written by ourselves, so we can make sure that doesn't happen. But we may to be a bit more robust and search for the last indexOf [[ and ]]. You can use lastIndexOf for this.

Copy link
Member

Choose a reason for hiding this comment

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

I'm in agreement that scanning for this substring seems uncomfortably fragile. Template variables like you're already doing in datasource.tsx (or some other format that is definitely not valid PxL) are a more robust choice here since you're controlling the preset scripts in this same repository. px/cluster, as mentioned, has at least three [[something]]s in it, how do you know for sure which one should be used?

Copy link
Contributor

Choose a reason for hiding this comment

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

We could try creating some macro which is easy to find without collisions with user code. #45 (comment)

Signed-off-by: Kartik Pattaswamy <kpattaswamy@pixielabs.ai>
Copy link
Member

@NickLanam NickLanam left a comment

Choose a reason for hiding this comment

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

Largely LGTM, just a couple of questions (see below)

tsconfig.json Show resolved Hide resolved
defaultValue={scriptOptions[0]}
/>

{query.isTabular === true ? (
Copy link
Member

Choose a reason for hiding this comment

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

Since the other path is just an empty fragment, you can even just do query.isTabular && (...) and not have a ternary at all. React renders nothing at all if it's told to render a literal false, so a && b resolves to either rendering nothing or rendering b.


//Splits the script into two parts: 1. Script up till display columns, 2. Script after displaying columns
function splitScript(script: string): string[] {
const beforeDisplayCol = script.slice(0, script.indexOf('[['));
Copy link
Member

Choose a reason for hiding this comment

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

I'm in agreement that scanning for this substring seems uncomfortably fragile. Template variables like you're already doing in datasource.tsx (or some other format that is definitely not valid PxL) are a more robust choice here since you're controlling the preset scripts in this same repository. px/cluster, as mentioned, has at least three [[something]]s in it, how do you know for sure which one should be used?

Copy link
Contributor

@TarasPriadka TarasPriadka left a comment

Choose a reason for hiding this comment

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

Looks good overall. Had some small nits

src/types.ts Outdated
Comment on lines 27 to 28
isTabular: boolean;
columnOptions: Array<{ label: string; value: number }>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Make these fields optional since not all scripts may have these fields.

Suggested change
isTabular: boolean;
columnOptions: Array<{ label: string; value: number }>;
isTabular?: boolean;
columnOptions?: Array<{ label: string; value: number }>;

Copy link
Member

Choose a reason for hiding this comment

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

This is a good suggestion.

@@ -1,5 +1,6 @@
{
"name": "Example Query",
"description": "Example query from Pixie documentation.",
"script": "'''\nThis query graphs the request throughput from the nodes in your cluster which comes from Pixie docs tutorial(\\`https://docs.px.dev/tutorials/integrations/grafana/\\`)\nIf you are interested in learning more about how to write pxl scripts, go to https://docs.px.dev/tutorials/pxl-scripts/.\n\nThis query is for use with Grafana's Pixie Datasource Plugin only,\nas it uses Grafana macros for adding Grafana dashboard context.\n\n'''\n\n# Import Pixie's module for querying data.\nimport px\n\n# Load data from Pixie's \\`http_events\\` table into a Dataframe.\ndf = px.DataFrame(table='http_events', start_time=__time_from)\n\n# Add K8s metadata context.\ndf.service = df.ctx['service']\ndf.namespace = df.ctx['namespace']\n\n# Bin the 'time_' column using the interval provided by Grafana.\ndf.timestamp = px.bin(df.time_, __interval)\n\n# Group data by unique pairings of 'timestamp' and 'service'\n# and count the total number of requests per unique pairing.\nper_ns_df = df.groupby(['timestamp', 'service']).agg(\n throughput_total=('latency', px.count)\n)\n\n# Calculate throughput by dividing # of requests by the time interval.\nper_ns_df.request_throughput = per_ns_df.throughput_total / __interval\nper_ns_df.request_throughput = per_ns_df.request_throughput * 1e9\n\n# Rename 'timestamp' column to 'time_'. The Grafana plugin expects a 'time_'\n# column to display data in a Graph or Time series.\nper_ns_df.time_ = per_ns_df.timestamp\n\n# Output select columns of the DataFrame.\npx.display(per_ns_df['time_', 'service', 'request_throughput'])\n\n"
"script": "'''\nThis query graphs the request throughput from the nodes in your cluster which comes from Pixie docs tutorial(\\`https://docs.px.dev/tutorials/integrations/grafana/\\`)\nIf you are interested in learning more about how to write pxl scripts, go to https://docs.px.dev/tutorials/pxl-scripts/.\n\nThis query is for use with Grafana's Pixie Datasource Plugin only,\nas it uses Grafana macros for adding Grafana dashboard context.\n\n'''\n\n# Import Pixie's module for querying data.\nimport px\n\n# Load data from Pixie's \\`http_events\\` table into a Dataframe.\ndf = px.DataFrame(table='http_events', start_time=__time_from)\n\n# Add K8s metadata context.\ndf.service = df.ctx['service']\ndf.namespace = df.ctx['namespace']\n\n# Bin the 'time_' column using the interval provided by Grafana.\ndf.timestamp = px.bin(df.time_, __interval)\n\n# Group data by unique pairings of 'timestamp' and 'service'\n# and count the total number of requests per unique pairing.\nper_ns_df = df.groupby(['timestamp', 'service']).agg(\n throughput_total=('latency', px.count)\n)\n\n# Calculate throughput by dividing # of requests by the time interval.\nper_ns_df.request_throughput = per_ns_df.throughput_total / __interval\nper_ns_df.request_throughput = per_ns_df.request_throughput * 1e9\n\n# Rename 'timestamp' column to 'time_'. The Grafana plugin expects a 'time_'\n# column to display data in a Graph or Time series.\nper_ns_df.time_ = per_ns_df.timestamp\n\n# Output select columns of the DataFrame.\npx.display(per_ns_df['time_', 'service', 'request_throughput'])\n\n",
"isTabular": false
Copy link
Contributor

Choose a reason for hiding this comment

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

If isTabular is optional in the query object, you don't have to add this field to every script. Same goes for all changed script files.


//Splits the script into two parts: 1. Script up till display columns, 2. Script after displaying columns
function splitScript(script: string): string[] {
const beforeDisplayCol = script.slice(0, script.lastIndexOf('[['));
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we can introduce some sort of macro which users can use to specify the column names? We can find that macro then and process it in a predictable way.
Ex.

...
COLUMNS{all} // or COLUMNS{'pod', 'namespace', 'latency', ...}
...

Then later find this macro in code, process the columns that users want to display, and replace this macro with df.display in query.pxlScript before sending the code query.

Comment on lines 54 to 58
//Dynamically builds the script by concatenating each option that was chosen
filteredColumnScript = `[[`;

for (let i = 0; i < chosenOptions.length; i++) {
filteredColumnScript += formatColumnName(chosenOptions[i].label);
Copy link
Contributor

Choose a reason for hiding this comment

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

You already have a method which does this.

Suggested change
//Dynamically builds the script by concatenating each option that was chosen
filteredColumnScript = `[[`;
for (let i = 0; i < chosenOptions.length; i++) {
filteredColumnScript += formatColumnName(chosenOptions[i].label);
//Dynamically builds the script by concatenating each option that was chosen
filteredColumnScript = getAllColumnScript(chosenOptions);

Comment on lines 33 to 41
//Returns a script which can be used to display all columns
function getAllColumnScript(columnNames: Array<{ label: string; value: number }>): string {
let script = '[[';

for (let i = 0; i < columnNames.length; i++) {
script += formatColumnName(columnNames[i].label);
}
return script;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can join an array of values, and also should probably name this something general like getScriptFromColumns:

Suggested change
//Returns a script which can be used to display all columns
function getAllColumnScript(columnNames: Array<{ label: string; value: number }>): string {
let script = '[[';
for (let i = 0; i < columnNames.length; i++) {
script += formatColumnName(columnNames[i].label);
}
return script;
}
function getScriptFromColumns(columnNames: Array<{ label: string; value: number }>): string {
return [
'[[',
...columnNames.map(colName=>`'${colName.label}'`),
']]'
].join()
}

This basically constructs your string where the mid part just formats colNames the way you want.

Comment on lines 35 to 36
timeVars.forEach((replace) => {
query.pxlScript = query.pxlScript.replaceAll(replace[0], replace[1]);
Copy link
Contributor

Choose a reason for hiding this comment

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

For readability, can do something like

Suggested change
timeVars.forEach((replace) => {
query.pxlScript = query.pxlScript.replaceAll(replace[0], replace[1]);
for (const [changeFrom, changeTo] of timeVars) {
query.pxlScript = query.pxlScript.replaceAll(replaceFrom, replaceTo);
}

Comment on lines 31 to 32
isTabular: boolean;
columnNames: string[];
Copy link
Contributor

Choose a reason for hiding this comment

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

Since these may be not present in some scripts, you can make these fields optional.

Suggested change
isTabular: boolean;
columnNames: string[];
isTabular?: boolean;
columnNames?: string[];

Comment on lines 43 to 44
isTabular: scriptObject.isTabular,
columnOptions: (scriptObject.columnNames || []).map((name, index) => ({ label: name, value: index })),
Copy link
Contributor

Choose a reason for hiding this comment

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

These are already a part of scriptObject, so you don't need to add these as fields. Also SelectableValue doesn't have these fields, so its better to avoid adding them directly to the object.

const { onChange, query, onRunQuery } = this.props;
onChange({ ...query, pxlScript: option.value });
const script = option.value;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const script = option.value;
const script:Script = option.value;

<MultiSelect
placeholder="Select columns to filter"
options={query.columnOptions}
onChange={this.filterColumns.bind(this)}
Copy link
Contributor

Choose a reason for hiding this comment

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

In React you want to call functions which get called on change or on click as on[name of action which is happening]. So in this case you could call this method onFilterSelect.

Signed-off-by: Kartik Pattaswamy <kpattaswamy@pixielabs.ai>

export class DataSource extends DataSourceWithBackend<PixieDataQuery, PixieDataSourceOptions> {
constructor(instanceSettings: DataSourceInstanceSettings<PixieDataSourceOptions>) {
super(instanceSettings);
}

applyTemplateVariables(query: PixieDataQuery, scopedVars: ScopedVars) {
let timeVars = [
Copy link
Member

Choose a reason for hiding this comment

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

Javascript has variables can be defined as let or const.
let is usually used when you know the variable is going to be reassigned:

let a = 1; 
a = 2;

However, const should be used when you essentially have a constant.

const a = 1;
a = 2; // You shouldn't do this!

In the case of timeVars, this can be a const. And columnVars below as well.
You can actually pull them out of this function and move them to the top of the file (defined on the same level as functions). In general, we like to pull out constants to the top so that they're easy to find and change if needbe.

['$__interval', '__interval'],
];

//Replace Grafana's time global variables from the script with Pixie's time macros
Copy link
Member

Choose a reason for hiding this comment

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

Nit: We like to make sure theres a space after the // in our comments. So this should be: // Replace Grafana's...

This is usually enforced by a linter, so let me look into why that's not set up yet so that it's easier to keep in mind for your next PRs.

src/types.ts Outdated
Comment on lines 27 to 28
isTabular: boolean;
columnOptions: Array<{ label: string; value: number }>;
Copy link
Member

Choose a reason for hiding this comment

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

This is a good suggestion.

Signed-off-by: Kartik Pattaswamy <kpattaswamy@pixielabs.ai>
TarasPriadka and others added 11 commits June 13, 2022 18:02
…milliseconds.

Signed-off-by: Taras Priadka <tpriadka@pixielabs.ai>
Signed-off-by: Taras Priadka <tpriadka@pixielabs.ai>
Signed-off-by: Taras Priadka <tpriadka@pixielabs.ai>
Signed-off-by: Kartik Pattaswamy <kpattaswamy@pixielabs.ai>
Signed-off-by: Kartik Pattaswamy <kpattaswamy@pixielabs.ai>
Signed-off-by: Kartik Pattaswamy <kpattaswamy@pixielabs.ai>
Signed-off-by: Kartik Pattaswamy <kpattaswamy@pixielabs.ai>
Signed-off-by: Kartik Pattaswamy <kpattaswamy@pixielabs.ai>
Signed-off-by: Kartik Pattaswamy <kpattaswamy@pixielabs.ai>
Signed-off-by: Kartik Pattaswamy <kpattaswamy@pixielabs.ai>
@aimichelle aimichelle merged commit 48f9ee8 into pixie-io:main Jun 14, 2022
@kpattaswamy kpattaswamy deleted the column-filtering branch June 14, 2022 22:51
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.

5 participants