mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Fleet] changed diagnostics query to speed up (#149812)
## Summary Changed query of diagnostics files to speed up seeing the files. This is because the agent has a delay of about 4m to ack the action, this has to be fixed separately, see here https://github.com/elastic/elastic-agent/pull/1703#issuecomment-1408250493 Related to https://github.com/elastic/kibana/issues/141074 We can search for the diagnostics file by `agent_id` and `action_id`, so don't have to wait for the `upload_id` which comes from `.fleet-actions-results`. https://user-images.githubusercontent.com/90178898/215451881-bfaa9e86-e055-4490-87b1-dc1d1076a738.mov Displaying error from agent when diagnostics failed: <img width="839" alt="image" src="https://user-images.githubusercontent.com/90178898/215476207-5db7e935-28dd-432e-a6a6-195da162028a.png"> E.g. `.fleet-files-agent` ``` { "_index": ".fleet-files-agent-000001", "_id": "8a004559-0731-4b8f-b29e-d7405ca0d68c.3a1f21b3-4559-4d3f-aae0-58356c269a92", "_score": null, "_source": { "action_id": "8a004559-0731-4b8f-b29e-d7405ca0d68c", "agent_id": "3a1f21b3-4559-4d3f-aae0-58356c269a92", "contents": null, "file": { "ChunkSize": 4194304, "Status": "READY", "ext": "zip", "hash": { "md5": "", "sha256": "" }, "mime_type": "application/zip", "name": "elastic-agent-diagnostics-2023-01-30T10-13-33Z-00.zip", "size": 577178 }, "src": "agent", "upload_id": "988da8ad-9d92-4d18-b5b0-b2a7e77f5a81", "upload_start": 1675073615066, "transithash": { "sha256": "8a417cc8a73e32723ff449b603412113f319c7447044e81acab3f57d4e8226c8" } }, ``` Changed the style to be more consistent: <img width="898" alt="image" src="https://user-images.githubusercontent.com/90178898/215492173-7362fab7-15e6-4de9-824b-239164512231.png"> ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
894b195f60
commit
3a961fb132
4 changed files with 110 additions and 22 deletions
|
@ -163,6 +163,7 @@ export interface AgentDiagnostics {
|
|||
filePath: string;
|
||||
status: 'READY' | 'AWAITING_UPLOAD' | 'DELETED' | 'IN_PROGRESS' | 'FAILED';
|
||||
actionId: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// Generated from FleetServer schema.json
|
||||
|
|
|
@ -37,6 +37,10 @@ const FlexStartEuiFlexItem = styled(EuiFlexItem)`
|
|||
align-self: flex-start;
|
||||
`;
|
||||
|
||||
const MarginedIcon = styled(EuiIcon)`
|
||||
margin-right: 7px;
|
||||
`;
|
||||
|
||||
export interface AgentDiagnosticsProps {
|
||||
agent: Agent;
|
||||
}
|
||||
|
@ -48,6 +52,7 @@ export const AgentDiagnosticsTab: React.FunctionComponent<AgentDiagnosticsProps>
|
|||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [diagnosticsEntries, setDiagnosticEntries] = useState<AgentDiagnostics[]>([]);
|
||||
const [prevDiagnosticsEntries, setPrevDiagnosticEntries] = useState<AgentDiagnostics[]>([]);
|
||||
const [loadInterval, setLoadInterval] = useState(10000);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
try {
|
||||
|
@ -59,8 +64,20 @@ export const AgentDiagnosticsTab: React.FunctionComponent<AgentDiagnosticsProps>
|
|||
if (!uploadsResponse.data) {
|
||||
throw new Error('No data');
|
||||
}
|
||||
setDiagnosticEntries(uploadsResponse.data.items);
|
||||
const entries = uploadsResponse.data.items;
|
||||
setDiagnosticEntries(entries);
|
||||
setIsLoading(false);
|
||||
|
||||
// query faster if an action is in progress, for quicker feedback
|
||||
if (
|
||||
entries.some(
|
||||
(entry) => entry.status === 'IN_PROGRESS' || entry.status === 'AWAITING_UPLOAD'
|
||||
)
|
||||
) {
|
||||
setLoadInterval(3000);
|
||||
} else {
|
||||
setLoadInterval(10000);
|
||||
}
|
||||
} catch (err) {
|
||||
notifications.toasts.addError(err, {
|
||||
title: i18n.translate(
|
||||
|
@ -71,13 +88,13 @@ export const AgentDiagnosticsTab: React.FunctionComponent<AgentDiagnosticsProps>
|
|||
),
|
||||
});
|
||||
}
|
||||
}, [agent.id, notifications.toasts]);
|
||||
}, [agent.id, notifications.toasts, setLoadInterval]);
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
const interval: ReturnType<typeof setInterval> | null = setInterval(async () => {
|
||||
loadData();
|
||||
}, 10000);
|
||||
}, loadInterval);
|
||||
|
||||
const cleanup = () => {
|
||||
if (interval) {
|
||||
|
@ -86,7 +103,7 @@ export const AgentDiagnosticsTab: React.FunctionComponent<AgentDiagnosticsProps>
|
|||
};
|
||||
|
||||
return cleanup;
|
||||
}, [loadData]);
|
||||
}, [loadData, loadInterval]);
|
||||
|
||||
useEffect(() => {
|
||||
setPrevDiagnosticEntries(diagnosticsEntries);
|
||||
|
@ -112,6 +129,9 @@ export const AgentDiagnosticsTab: React.FunctionComponent<AgentDiagnosticsProps>
|
|||
}
|
||||
}, [prevDiagnosticsEntries, diagnosticsEntries, notifications.toasts]);
|
||||
|
||||
const errorIcon = <MarginedIcon type="alert" color="red" />;
|
||||
const getErrorMessage = (error?: string) => (error ? `Error: ${error}` : '');
|
||||
|
||||
const columns: Array<EuiTableFieldDataColumnType<AgentDiagnostics>> = [
|
||||
{
|
||||
field: 'id',
|
||||
|
@ -123,21 +143,32 @@ export const AgentDiagnosticsTab: React.FunctionComponent<AgentDiagnosticsProps>
|
|||
<EuiIcon type="download" /> {currentItem?.name}
|
||||
</EuiLink>
|
||||
) : currentItem?.status === 'IN_PROGRESS' || currentItem?.status === 'AWAITING_UPLOAD' ? (
|
||||
<EuiText color="subdued">
|
||||
<EuiLink color="subdued" disabled>
|
||||
<EuiLoadingSpinner />
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.requestDiagnostics.generatingText"
|
||||
defaultMessage="Generating diagnostics file..."
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiLink>
|
||||
) : (
|
||||
<EuiText color="subdued">
|
||||
<EuiToolTip content={`Diagnostics status: ${currentItem?.status}`}>
|
||||
<EuiIcon type="alert" color="red" />
|
||||
</EuiToolTip>
|
||||
<EuiLink color="subdued" disabled>
|
||||
{currentItem?.status ? (
|
||||
<EuiToolTip
|
||||
content={
|
||||
<>
|
||||
<p>Diagnostics status: {currentItem?.status}</p>
|
||||
<p>{getErrorMessage(currentItem?.error)}</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{errorIcon}
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
errorIcon
|
||||
)}
|
||||
|
||||
{currentItem?.name}
|
||||
</EuiText>
|
||||
</EuiLink>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
@ -149,7 +180,7 @@ export const AgentDiagnosticsTab: React.FunctionComponent<AgentDiagnosticsProps>
|
|||
const currentItem = diagnosticsEntries.find((item) => item.id === id);
|
||||
return (
|
||||
<EuiText color={currentItem?.status === 'READY' ? 'default' : 'subdued'}>
|
||||
{formatDate(currentItem?.createTime, 'll')}
|
||||
{formatDate(currentItem?.createTime, 'lll')}
|
||||
</EuiText>
|
||||
);
|
||||
},
|
||||
|
@ -171,6 +202,7 @@ export const AgentDiagnosticsTab: React.FunctionComponent<AgentDiagnosticsProps>
|
|||
}
|
||||
);
|
||||
notifications.toasts.addSuccess(successMessage);
|
||||
loadData();
|
||||
} catch (error) {
|
||||
setIsSubmitting(false);
|
||||
notifications.toasts.addError(error, {
|
||||
|
|
|
@ -31,21 +31,24 @@ export async function getAgentUploads(
|
|||
esClient: ElasticsearchClient,
|
||||
agentId: string
|
||||
): Promise<AgentDiagnostics[]> {
|
||||
const getFile = async (fileId: string) => {
|
||||
if (!fileId) return;
|
||||
const getFile = async (actionId: string) => {
|
||||
try {
|
||||
const fileResponse = await esClient.search({
|
||||
index: FILE_STORAGE_METADATA_AGENT_INDEX,
|
||||
query: {
|
||||
bool: {
|
||||
filter: {
|
||||
term: { upload_id: fileId },
|
||||
bool: {
|
||||
must: [{ term: { agent_id: agentId } }, { term: { action_id: actionId } }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (fileResponse.hits.total === 0) {
|
||||
appContextService.getLogger().debug(`No matches for upload_id ${fileId}`);
|
||||
if (fileResponse.hits.hits.length === 0) {
|
||||
appContextService
|
||||
.getLogger()
|
||||
.debug(`No matches for action_id ${actionId} and agent_id ${agentId}`);
|
||||
return;
|
||||
}
|
||||
return {
|
||||
|
@ -64,10 +67,14 @@ export async function getAgentUploads(
|
|||
|
||||
const actions = await _getRequestDiagnosticsActions(esClient, agentId);
|
||||
|
||||
const results = [];
|
||||
const results: AgentDiagnostics[] = [];
|
||||
for (const action of actions) {
|
||||
const file = action.fileId ? await getFile(action.fileId) : undefined;
|
||||
const fileName = file?.name ?? `${moment(action.timestamp!).format('YYYY-MM-DD HH:mm:ss')}.zip`;
|
||||
const file = await getFile(action.actionId);
|
||||
const fileName =
|
||||
file?.name ??
|
||||
`elastic-agent-diagnostics-${moment
|
||||
.utc(action.timestamp!)
|
||||
.format('YYYY-MM-DDTHH-mm-ss')}Z-00.zip`;
|
||||
const filePath = file ? agentRouteService.getAgentFileDownloadLink(file.id, file.name) : '';
|
||||
const result = {
|
||||
actionId: action.actionId,
|
||||
|
@ -76,6 +83,7 @@ export async function getAgentUploads(
|
|||
name: fileName,
|
||||
createTime: action.timestamp!,
|
||||
filePath,
|
||||
error: action.error,
|
||||
};
|
||||
results.push(result);
|
||||
}
|
||||
|
@ -91,6 +99,7 @@ async function _getRequestDiagnosticsActions(
|
|||
index: AGENT_ACTIONS_INDEX,
|
||||
ignore_unavailable: true,
|
||||
size: SO_SEARCH_LIMIT,
|
||||
sort: { '@timestamp': 'desc' },
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
|
@ -150,7 +159,7 @@ async function _getRequestDiagnosticsActions(
|
|||
const actionResult = actionResults.find((result) => result.actionId === action.actionId);
|
||||
return {
|
||||
actionId: action.actionId,
|
||||
timestamp: actionResult?.timestamp ?? action.timestamp,
|
||||
timestamp: action.timestamp,
|
||||
fileId: actionResult?.fileId,
|
||||
error: actionResult?.error,
|
||||
};
|
||||
|
|
|
@ -68,6 +68,8 @@ export default function (providerContext: FtrProviderContext) {
|
|||
doc_as_upsert: true,
|
||||
doc: {
|
||||
upload_id: 'file1',
|
||||
action_id: 'action1',
|
||||
agent_id: 'agent1',
|
||||
file: {
|
||||
ChunkSize: 4194304,
|
||||
extension: 'zip',
|
||||
|
@ -96,7 +98,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
|
||||
expect(body.items[0]).to.eql({
|
||||
actionId: 'action1',
|
||||
createTime: '2022-10-07T12:00:00.000Z',
|
||||
createTime: '2022-10-07T11:00:00.000Z',
|
||||
filePath:
|
||||
'/api/fleet/agents/files/file1/elastic-agent-diagnostics-2022-10-07T12-00-00Z-00.zip',
|
||||
id: 'file1',
|
||||
|
@ -130,5 +132,49 @@ export default function (providerContext: FtrProviderContext) {
|
|||
'attachment; filename="elastic-agent-diagnostics-2022-10-07T12-00-00Z-00.zip"'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return failed status with error message', async () => {
|
||||
await esClient.create({
|
||||
index: AGENT_ACTIONS_INDEX,
|
||||
id: new Date().toISOString(),
|
||||
refresh: true,
|
||||
body: {
|
||||
type: 'REQUEST_DIAGNOSTICS',
|
||||
action_id: 'action2',
|
||||
agents: ['agent2'],
|
||||
'@timestamp': '2022-10-07T11:00:00.000Z',
|
||||
},
|
||||
});
|
||||
await esClient.create(
|
||||
{
|
||||
index: AGENT_ACTIONS_RESULTS_INDEX,
|
||||
id: new Date().toISOString(),
|
||||
refresh: true,
|
||||
body: {
|
||||
action_id: 'action2',
|
||||
agent_id: 'agent2',
|
||||
'@timestamp': '2022-10-07T12:00:00.000Z',
|
||||
data: {},
|
||||
error: 'rate limit exceeded',
|
||||
},
|
||||
},
|
||||
ES_INDEX_OPTIONS
|
||||
);
|
||||
|
||||
const { body } = await supertest
|
||||
.get(`/api/fleet/agents/agent2/uploads`)
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.expect(200);
|
||||
|
||||
expect(body.items[0]).to.eql({
|
||||
actionId: 'action2',
|
||||
createTime: '2022-10-07T11:00:00.000Z',
|
||||
filePath: '',
|
||||
id: 'action2',
|
||||
name: 'elastic-agent-diagnostics-2022-10-07T11-00-00Z-00.zip',
|
||||
status: 'FAILED',
|
||||
error: 'rate limit exceeded',
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue