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

ft: Filter by function functionality added to ProfileView #2062

Merged
merged 12 commits into from
Nov 14, 2022
582 changes: 297 additions & 285 deletions gen/proto/go/parca/query/v1alpha1/query.pb.go

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions gen/proto/go/parca/query/v1alpha1/query_vtproto.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions gen/proto/swagger/parca/query/v1alpha1/query.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,13 @@
"REPORT_TYPE_FLAMEGRAPH_TABLE"
],
"default": "REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED"
},
{
"name": "filterQuery",
"description": "filter_query is the query string to filter the profile samples",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
Expand Down Expand Up @@ -688,6 +695,10 @@
"reportType": {
"$ref": "#/definitions/QueryRequestReportType",
"title": "report_type is the type of report to return"
},
"filterQuery": {
"type": "string",
"title": "filter_query is the query string to filter the profile samples"
}
},
"title": "QueryRequest is a request for a profile query"
Expand Down
26 changes: 26 additions & 0 deletions pkg/query/columnquery.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"bytes"
"context"
"fmt"
"strings"
"time"

"github.com/go-kit/log"
Expand Down Expand Up @@ -142,9 +143,33 @@ func (q *ColumnQueryAPI) Query(ctx context.Context, req *pb.QueryRequest) (*pb.Q
return nil, err
}

if req.FilterQuery != nil {
p = filterProfileData(p, *req.FilterQuery)
}

return q.renderReport(ctx, p, req.GetReportType())
}

func filterProfileData(p *profile.Profile, filterQuery string) *profile.Profile {
filteredSamples := []*profile.SymbolizedSample{}
for _, s := range p.Samples {
var lines []profile.LocationLine
Copy link
Member

Choose a reason for hiding this comment

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

We don't need to copy here. I would suggest the inside of this loop should be extracted to a separate function that's just called keepSample and if it returns true, then we append to filteredSamples. All the keepSample function then does is loop over all lines of all locations and on the first match it returns true and otherwise if no location line contained the search string, then it returns false.

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

for _, loc := range s.Locations {
lines = append(lines, loc.Lines...)
}
for _, l := range lines {
if l.Function != nil && strings.Contains(strings.ToLower(l.Function.Name), strings.ToLower(filterQuery)) {
filteredSamples = append(filteredSamples, s)
break
}
}
}
return &profile.Profile{
Samples: filteredSamples,
Meta: p.Meta,
}
}

func (q *ColumnQueryAPI) renderReport(ctx context.Context, p *profile.Profile, typ pb.QueryRequest_ReportType) (*pb.QueryResponse, error) {
ctx, span := q.tracer.Start(ctx, "renderReport")
span.SetAttributes(attribute.String("reportType", typ.String()))
Expand Down Expand Up @@ -196,6 +221,7 @@ func (q *ColumnQueryAPI) renderReport(ctx context.Context, p *profile.Profile, t
Report: &pb.QueryResponse_Top{Top: top},
}, nil
case pb.QueryRequest_REPORT_TYPE_CALLGRAPH:
fmt.Println("callgraph")
Copy link
Member

Choose a reason for hiding this comment

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

debugging leftover I assume?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh, yes, my bad.

callgraph, err := GenerateCallgraph(ctx, p)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to generate callgraph: %v", err.Error())
Expand Down
28 changes: 28 additions & 0 deletions pkg/query/columnquery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ func TestColumnQueryAPIQueryRange(t *testing.T) {
require.Equal(t, 10, len(res.Series[0].Samples))
}

func ptrToString(s string) *string {
return &s
}

func TestColumnQueryAPIQuerySingle(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -304,6 +308,30 @@ func TestColumnQueryAPIQuerySingle(t *testing.T) {
})
require.NoError(t, err)

unfilteredRes, err := api.Query(ctx, &pb.QueryRequest{
ReportType: pb.QueryRequest_REPORT_TYPE_TOP,
Options: &pb.QueryRequest_Single{
Single: &pb.SingleProfile{
Query: `memory:alloc_objects:count:space:bytes{job="default"}`,
Time: ts,
},
},
})
require.NoError(t, err)

filteredRes, err := api.Query(ctx, &pb.QueryRequest{
ReportType: pb.QueryRequest_REPORT_TYPE_TOP,
Options: &pb.QueryRequest_Single{
Single: &pb.SingleProfile{
Query: `memory:alloc_objects:count:space:bytes{job="default", __name__="memory"}`,
Time: ts,
},
},
FilterQuery: ptrToString("runtime"),
})
require.NoError(t, err)
require.Less(t, len(filteredRes.Report.(*pb.QueryResponse_Top).Top.List), len(unfilteredRes.Report.(*pb.QueryResponse_Top).Top.List), "filtered result should be smaller than unfiltered result")

testProf := &pprofpb.Profile{}
err = testProf.UnmarshalVT(MustDecompressGzip(t, res.Report.(*pb.QueryResponse_Pprof).Pprof))
require.NoError(t, err)
Expand Down
3 changes: 3 additions & 0 deletions proto/parca/query/v1alpha1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ message QueryRequest {

// report_type is the type of report to return
ReportType report_type = 5;

// filter_query is the query string to filter the profile samples
optional string filter_query = 6;
}

// Top is the top report type
Expand Down
15 changes: 14 additions & 1 deletion ui/packages/shared/client/src/parca/query/v1alpha1/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,12 @@ export interface QueryRequest {
* @generated from protobuf field: parca.query.v1alpha1.QueryRequest.ReportType report_type = 5;
*/
reportType: QueryRequest_ReportType;
/**
* filter_query is the query string to filter the profile samples
*
* @generated from protobuf field: optional string filter_query = 6;
*/
filterQuery?: string;
}
/**
* Mode is the type of query request
Expand Down Expand Up @@ -1633,7 +1639,8 @@ class QueryRequest$Type extends MessageType<QueryRequest> {
{ no: 2, name: "diff", kind: "message", oneof: "options", T: () => DiffProfile },
{ no: 3, name: "merge", kind: "message", oneof: "options", T: () => MergeProfile },
{ no: 4, name: "single", kind: "message", oneof: "options", T: () => SingleProfile },
{ no: 5, name: "report_type", kind: "enum", T: () => ["parca.query.v1alpha1.QueryRequest.ReportType", QueryRequest_ReportType, "REPORT_TYPE_"] }
{ no: 5, name: "report_type", kind: "enum", T: () => ["parca.query.v1alpha1.QueryRequest.ReportType", QueryRequest_ReportType, "REPORT_TYPE_"] },
{ no: 6, name: "filter_query", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<QueryRequest>): QueryRequest {
Expand Down Expand Up @@ -1672,6 +1679,9 @@ class QueryRequest$Type extends MessageType<QueryRequest> {
case /* parca.query.v1alpha1.QueryRequest.ReportType report_type */ 5:
message.reportType = reader.int32();
break;
case /* optional string filter_query */ 6:
message.filterQuery = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
Expand Down Expand Up @@ -1699,6 +1709,9 @@ class QueryRequest$Type extends MessageType<QueryRequest> {
/* parca.query.v1alpha1.QueryRequest.ReportType report_type = 5; */
if (message.reportType !== 0)
writer.tag(5, WireType.Varint).int32(message.reportType);
/* optional string filter_query = 6; */
if (message.filterQuery !== undefined)
writer.tag(6, WireType.LengthDelimited).string(message.filterQuery);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
Expand Down
1 change: 1 addition & 0 deletions ui/packages/shared/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"dependencies": {
"@headlessui/react": "^1.4.3",
"@heroicons/react": "^1.0.5",
"@iconify/react": "^3.2.2",
"@parca/client": "^0.16.54",
"@parca/dynamicsize": "^0.16.51",
"@parca/functions": "^0.16.51",
Expand Down
2 changes: 1 addition & 1 deletion ui/packages/shared/components/src/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const Button = ({
className={cx(
disabled ? 'opacity-50 pointer-events-none' : '',
...Object.values(BUTTON_VARIANT[variant]),
'cursor-pointer group relative w-full flex $ text-sm rounded-md text-whitefocus:outline-none focus:ring-2 focus:ring-offset-2',
'cursor-pointer group relative w-full flex $ text-sm rounded-md text-whitefocus:outline-none focus:ring-2 focus:ring-offset-2 items-center justify-center',
className
)}
disabled={disabled}
Expand Down
71 changes: 61 additions & 10 deletions ui/packages/shared/components/src/Input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,70 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {Icon} from '@iconify/react';
import Button from '../Button';
import cx from 'classnames';
import {useRef} from 'react';

const Input = ({className = '', ...props}): JSX.Element => {
interface SelfProps {
className?: string;
onAction?: () => void;
actionIcon?: JSX.Element;
}

export type Props = React.InputHTMLAttributes<HTMLInputElement> & SelfProps;

const Input = ({
className = '',
onAction,
actionIcon = <Icon icon="ep:arrow-right" />,
onBlur,
...props
}: Props): JSX.Element => {
const ref = useRef<HTMLInputElement>(null);
return (
<input
{...props}
className={cx(
'p-2 rounded-md bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-600',
{
[className]: className.length > 0,
}
)}
/>
<div
className="relative"
ref={ref}
onBlur={e => {
(async () => {
if (onBlur == null || ref.current == null) {
return;
}
await new Promise(resolve => setTimeout(resolve));
if (ref.current.contains(document.activeElement)) {
return;
}
onBlur(e as React.FocusEvent<HTMLInputElement>);
})().catch(err => {
console.error('Error in processing blur event', err);
});
}}
>
<input
{...props}
className={cx(
'p-2 rounded-md bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-600',
{
[className]: className.length > 0,
'pr-10': onAction != null,
}
)}
onKeyDown={e => {
if (e.key === 'Enter' && onAction != null) {
onAction();
}
}}
/>
{onAction != null ? (
<Button
onClick={onAction}
className="!absolute w-fit inset-y-0 right-0 !px-2 aspect-square rounded-tl-none rounded-bl-none"
>
{actionIcon}
</Button>
) : null}
</div>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ A Component to select a range of dates with time.
</Story>
</Preview>

### With Action

<Preview>
<Story name="type=number">
<div className="p-8">
<Input type="number" defaultValue="1" onAction={() => {}}/>
</div>
</Story>
</Preview>

## Props

<Props of={Input} />
8 changes: 7 additions & 1 deletion ui/packages/shared/components/src/SearchNodes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
import {Input} from '../';
import {useEffect, useMemo} from 'react';
import {useAppDispatch, setSearchNodeString} from '@parca/store';
import useUIFeatureFlag from '@parca/functions/useUIFeatureFlag';
import {debounce} from 'lodash';

const SearchNodes = (): JSX.Element => {
const dispatch = useAppDispatch();
const [filterByFunctionEnabled] = useUIFeatureFlag('filterByFunction');

useEffect(() => {
return () => {
Expand All @@ -35,7 +37,11 @@ const SearchNodes = (): JSX.Element => {

return (
<div>
<Input className="text-sm" placeholder="Search nodes..." onChange={debouncedSearch}></Input>
<Input
className="text-sm"
placeholder={filterByFunctionEnabled ? 'Highlight nodes...' : 'Search nodes...'}
onChange={debouncedSearch}
></Input>
</div>
);
};
Expand Down
6 changes: 3 additions & 3 deletions ui/packages/shared/profile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"version": "0.16.64",
"description": "Profile viewing libraries",
"dependencies": {
"@iconify/react": "^3.2.2",
"@parca/client": "^0.16.54",
"@parca/components": "^0.16.59",
"@parca/dynamicsize": "^0.16.51",
Expand All @@ -30,8 +29,9 @@
"scripts": {
"test": "jest --coverage --config ../../../jest.config.js ./src/*",
"prepublish": "yarn build",
"build": "tsc && tailwindcss -o dist/styles.css --minify && cp src/*.css ./dist/",
"watch": "tsc-watch --onSuccess 'tailwindcss -o dist/styles.css --minify'"
"build": "tsc && yarn compile:styles",
"watch": "tsc-watch --onSuccess 'yarn compile:styles'",
"compile:styles": "tailwindcss -o dist/styles.css --minify && cp src/*.css ./dist/"
},
"keywords": [],
"author": "",
Expand Down
Loading