Skip to content

Commit

Permalink
feat(frontend): adds ability to export to flamegraph.com (#799)
Browse files Browse the repository at this point in the history
* feat(frontend) adds ability to export to flamegraph.com

* fests fix

* adds flamegraph.com export to comparison view as well
  • Loading branch information
petethepig authored Feb 7, 2022
1 parent c687f83 commit a3828bc
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 2 deletions.
1 change: 1 addition & 0 deletions pkg/server/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ func (ctrl *Controller) mux() (http.Handler, error) {
{"/render-diff", ctrl.renderDiffHandler},
{"/labels", ctrl.labelsHandler},
{"/label-values", ctrl.labelValuesHandler},
{"/export", ctrl.exportHandler},
{"/api/adhoc", ctrl.adhoc.AddRoutes(r.PathPrefix("/api/adhoc").Subrouter())},
}
ctrl.addRoutes(r, protectedRoutes, ctrl.drainMiddleware, ctrl.authMiddleware)
Expand Down
25 changes: 25 additions & 0 deletions pkg/server/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package server

import (
"fmt"
"io"
"net/http"
)

type Payload struct {
Name string
Profile string
Type string
}

func (ctrl *Controller) exportHandler(w http.ResponseWriter, r *http.Request) {
resp, err := http.Post("https://flamegraph.com/api/upload/v1", "application/json", r.Body)
if err != nil {
ctrl.writeError(w, 500, err, fmt.Sprintf("could not upload profile: %v", err))
return
}
defer resp.Body.Close()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
io.Copy(w, resp.Body)
}
2 changes: 2 additions & 0 deletions webapp/javascript/components/ComparisonApp.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function ComparisonApp(props) {
exportJSON
exportHTML
exportPprof
exportFlamegraphDotCom
/>
}
>
Expand Down Expand Up @@ -97,6 +98,7 @@ function ComparisonApp(props) {
exportJSON
exportHTML
exportPprof
exportFlamegraphDotCom
/>
}
>
Expand Down
90 changes: 88 additions & 2 deletions webapp/javascript/components/ExportData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,53 @@ type exportHTML =
}
| { exportHTML?: false };

type exportFlamegraphDotCom =
| {
exportFlamegraphDotCom: true;
fetchUrlFunc?: () => string;
flamebearer: RawFlamebearerProfile;
}
| { exportFlamegraphDotCom?: false };

type exportPNG =
| {
exportPNG: true;
flamebearer: RawFlamebearerProfile;
}
| { exportPNG?: false };

type ExportDataProps = exportPprof & exportJSON & exportHTML & exportPNG;
type ExportDataProps = exportPprof &
exportJSON &
exportHTML &
exportFlamegraphDotCom &
exportPNG;

// handleResponse retrieves the JSON data on success or raises an ResponseNotOKError otherwise
// TODO: dedup this with the one in actions.js
function handleResponse(response) {
if (response.ok) {
return response.json();
}
return response.text().then((text) => {
throw new Error(`Bad Response with code ${response.status}: ${text}`);
});
}

function ExportData(props: ExportDataProps) {
const {
exportPprof = false,
exportJSON = false,
exportPNG = false,
exportHTML = false,
exportFlamegraphDotCom = false,
} = props;
if (!exportPNG && !exportJSON && !exportPprof && !exportHTML) {
if (
!exportPNG &&
!exportJSON &&
!exportPprof &&
!exportHTML &&
!exportFlamegraphDotCom
) {
throw new Error('At least one export button should be enabled');
}

Expand Down Expand Up @@ -82,6 +112,52 @@ function ExportData(props: ExportDataProps) {
}
};

const downloadFlamegraphDotCom = () => {
if (!props.exportFlamegraphDotCom) {
return;
}

// TODO additional check this won't be needed once we use strictNullChecks
if (props.exportFlamegraphDotCom) {
const { flamebearer } = props;

const filename = `${getFilename(
flamebearer.metadata.appName,
flamebearer.metadata.startTime,
flamebearer.metadata.endTime
)}.png`;

// we don't upload to flamegraph.com directly due to potential CORS issues
fetch('/export', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: filename,
profile: btoa(JSON.stringify(flamebearer)),
type: 'application/json',
}),
})
.then((response) => handleResponse(response))
.then((json) => {
const dlLink = document.createElement('a');
dlLink.target = '_blank';
dlLink.href = json.url;

document.body.appendChild(dlLink);
dlLink.click();
document.body.removeChild(dlLink);
})
.catch((e) => {
console.error(e);
// TODO: show error to user via notifications
})
.finally();
}
};

const downloadPNG = () => {
if (props.exportPNG) {
const { flamebearer } = props;
Expand Down Expand Up @@ -242,6 +318,16 @@ function ExportData(props: ExportDataProps) {
html
</button>
)}
{exportFlamegraphDotCom && (
<button
className="dropdown-menu-item"
type="button"
onClick={() => downloadFlamegraphDotCom()}
>
{' '}
flamegraph.com
</button>
)}
</div>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions webapp/javascript/components/PyroscopeApp.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ function PyroscopeApp(props) {
exportJSON
exportPprof
exportHTML
exportFlamegraphDotCom
/>
}
/>
Expand Down

0 comments on commit a3828bc

Please sign in to comment.