Skip to content

Commit da30750

Browse files
stroxlerfacebook-github-bot
authored andcommitted
Use DaemonStatus to encapsulate time + connection status
Summary: We always want both the connection status before and information about how long it has been since the server is ready; this combination of information makes it much easier to judge how big a problem availability is, because - we can't decide whether incremental status is bad or not *at all* without a timer, since it's expected that we'll be briefly busy - even for other statuses like starting, it's very helpful to have a record of how long start took; this, for example, can help us distinguish between an availability problem due primarily to long start times versus a problem due primarilily to frequent deamon (or persistent) restarts Putting these fields in the same dataclass makes it easier to ensure we always do the right thing, and that the logging is consistent across all telemetry events. It's worth noting that I caught another bug here: we weren't logging status in the telemetry for type coverage; this isn't a big deal today but it could be one if we ever start seeing heavy use of expression-level coverage. Reviewed By: grievejia Differential Revision: D43244195 fbshipit-source-id: d5ff1b0a39e2b09e3873a3131373a7f466c77d5d
1 parent 564e2bb commit da30750

File tree

2 files changed

+39
-43
lines changed

2 files changed

+39
-43
lines changed

client/commands/pyre_language_server.py

Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -289,11 +289,9 @@ async def process_did_change_request(
289289
if document_path not in self.server_state.opened_documents:
290290
return
291291

292-
time_since_last_ready_ms = (
293-
self.server_state.status_tracker.milliseconds_not_ready()
294-
)
295-
server_status_before = self.server_state.status_tracker.get_status().value
292+
daemon_status_before = self.server_state.status_tracker.get_status()
296293
did_change_timer = timer.Timer()
294+
297295
process_unsaved_changes = (
298296
self.server_state.server_options.language_server_features.unsaved_changes.is_enabled()
299297
)
@@ -324,10 +322,9 @@ async def process_did_change_request(
324322
self.server_state.opened_documents
325323
),
326324
"duration_ms": did_change_timer.stop_in_millisecond(),
327-
"time_since_last_ready_ms": time_since_last_ready_ms,
328-
"server_status_before": str(server_status_before),
329325
"error_message": error_message,
330326
"overlays_enabled": process_unsaved_changes,
327+
**daemon_status_before.as_telemetry_dict(),
331328
},
332329
activity_key,
333330
)
@@ -347,10 +344,7 @@ async def process_did_save_request(
347344
if document_path not in self.server_state.opened_documents:
348345
return
349346

350-
time_since_last_ready_ms = (
351-
self.server_state.status_tracker.milliseconds_not_ready()
352-
)
353-
server_status_before = self.server_state.status_tracker.get_status().value
347+
daemon_status_before = self.server_state.status_tracker.get_status()
354348

355349
code_changes = self.server_state.opened_documents[document_path].code
356350

@@ -371,11 +365,10 @@ async def process_did_save_request(
371365
"server_state_open_documents_count": len(
372366
self.server_state.opened_documents
373367
),
374-
"server_status_before": str(server_status_before),
375-
"time_since_last_ready_ms": time_since_last_ready_ms,
376368
# We don't do any blocking work on didSave, but analytics are easier if
377369
# we avoid needlessly introducing NULL values.
378370
"duration_ms": 0,
371+
**daemon_status_before.as_telemetry_dict(),
379372
},
380373
activity_key,
381374
)
@@ -392,11 +385,10 @@ async def process_type_coverage_request(
392385
f"Document URI is not a file: {parameters.text_document.uri}"
393386
)
394387
document_path = document_path.resolve()
395-
time_since_last_ready_ms = (
396-
self.server_state.status_tracker.milliseconds_not_ready()
397-
)
398-
server_status_before = self.server_state.status_tracker.get_status().value
388+
389+
daemon_status_before = self.server_state.status_tracker.get_status()
399390
type_coverage_timer = timer.Timer()
391+
400392
response = await self.querier.get_type_coverage(path=document_path)
401393
if response is not None:
402394
await lsp.write_json_rpc(
@@ -413,12 +405,8 @@ async def process_type_coverage_request(
413405
"operation": "typeCoverage",
414406
"filePath": str(document_path),
415407
"duration_ms": type_coverage_timer.stop_in_millisecond(),
416-
"time_since_last_ready_ms": time_since_last_ready_ms,
417-
"server_state_open_documents_count": len(
418-
self.server_state.opened_documents
419-
),
420-
"server_status_before": str(server_status_before),
421408
"coverage_type": self.get_language_server_features().type_coverage.value,
409+
**daemon_status_before.as_telemetry_dict(),
422410
},
423411
activity_key,
424412
)
@@ -451,11 +439,9 @@ async def process_hover_request(
451439
),
452440
)
453441
else:
454-
time_since_last_ready_ms = (
455-
self.server_state.status_tracker.milliseconds_not_ready()
456-
)
457-
server_status_before = self.server_state.status_tracker.get_status().value
442+
daemon_status_before = self.server_state.status_tracker.get_status()
458443
hover_timer = timer.Timer()
444+
459445
await self.update_overlay_if_needed(document_path)
460446
result = await self.querier.get_hover(
461447
path=document_path,
@@ -489,13 +475,12 @@ async def process_hover_request(
489475
"nonEmpty": len(result.contents) > 0,
490476
"response": raw_result,
491477
"duration_ms": hover_timer.stop_in_millisecond(),
492-
"time_since_last_ready_ms": time_since_last_ready_ms,
493478
"server_state_open_documents_count": len(
494479
self.server_state.opened_documents
495480
),
496-
"server_status_before": str(server_status_before),
497481
"error_message": error_message,
498482
"overlays_enabled": self.server_state.server_options.language_server_features.unsaved_changes.is_enabled(),
483+
**daemon_status_before.as_telemetry_dict(),
499484
},
500485
activity_key,
501486
)
@@ -559,10 +544,7 @@ async def process_definition_request(
559544
result=lsp.LspLocation.cached_schema().dump([], many=True),
560545
),
561546
)
562-
time_since_last_ready_ms = (
563-
self.server_state.status_tracker.milliseconds_not_ready()
564-
)
565-
server_status_before = self.server_state.status_tracker.get_status().value
547+
daemon_status_before = self.server_state.status_tracker.get_status()
566548
shadow_mode = self.get_language_server_features().definition.is_shadow()
567549
# In shadow mode, we need to return an empty response immediately
568550
if shadow_mode:
@@ -608,17 +590,16 @@ async def process_definition_request(
608590
"count": len(output_result),
609591
"response": output_result,
610592
"duration_ms": result_with_durations.overall_duration,
611-
"time_since_last_ready_ms": time_since_last_ready_ms,
612593
"overlay_update_duration": result_with_durations.overlay_update_duration,
613594
"query_duration": result_with_durations.query_duration,
614595
"server_state_open_documents_count": len(
615596
self.server_state.opened_documents
616597
),
617-
"server_status_before": str(server_status_before),
618598
"overlays_enabled": self.server_state.server_options.language_server_features.unsaved_changes.is_enabled(),
619599
"error_message": error_message,
620600
"is_dirty": self.server_state.opened_documents[document_path].is_dirty,
621601
"truncated_file_contents": source_code_if_sampled,
602+
**daemon_status_before.as_telemetry_dict(),
622603
},
623604
activity_key,
624605
)

client/commands/server_state.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
should be aware a change to this state could affect other modules that interact with this state.
1010
"""
1111

12+
from __future__ import annotations
1213

1314
import dataclasses
1415
import enum
@@ -33,6 +34,18 @@ class ConnectionStatus(enum.Enum):
3334
STARTING = "STARTING"
3435

3536

37+
@dataclasses.dataclass(frozen=True)
38+
class DaemonStatus:
39+
connection_status: ConnectionStatus
40+
milliseconds_since_ready: float
41+
42+
def as_telemetry_dict(self) -> Dict[str, float | str]:
43+
return {
44+
"server_state_before": self.connection_status.value,
45+
"time_since_last_ready_ms": self.milliseconds_since_ready,
46+
}
47+
48+
3649
@dataclasses.dataclass(frozen=True)
3750
class OpenedDocumentState:
3851
code: str
@@ -42,23 +55,25 @@ class OpenedDocumentState:
4255

4356
@dataclasses.dataclass
4457
class DaemonStatusTracker:
45-
_status: ConnectionStatus = ConnectionStatus.NOT_CONNECTED
58+
_connection_status: ConnectionStatus = ConnectionStatus.NOT_CONNECTED
4659
_not_ready_timer: Optional[timer.Timer] = None
4760

4861
def set_status(self, new_status: ConnectionStatus) -> None:
4962
if new_status == ConnectionStatus.READY:
5063
self._not_ready_timer = None
5164
elif self._not_ready_timer is None:
5265
self._not_ready_timer = timer.Timer()
53-
self._status = new_status
54-
55-
def get_status(self) -> ConnectionStatus:
56-
return self._status
57-
58-
def milliseconds_not_ready(self) -> int:
59-
if self._not_ready_timer is None:
60-
return 0
61-
return int(self._not_ready_timer.stop_in_millisecond())
66+
self._connection_status = new_status
67+
68+
def get_status(self) -> DaemonStatus:
69+
return DaemonStatus(
70+
connection_status=self._connection_status,
71+
milliseconds_since_ready=(
72+
0
73+
if self._not_ready_timer is None
74+
else self._not_ready_timer.stop_in_millisecond()
75+
),
76+
)
6277

6378

6479
@dataclasses.dataclass

0 commit comments

Comments
 (0)